Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-line node modules #10

Closed
hemanth opened this issue Jul 1, 2015 · 52 comments
Closed

One-line node modules #10

hemanth opened this issue Jul 1, 2015 · 52 comments

Comments

@hemanth
Copy link

@hemanth hemanth commented Jul 1, 2015

Lot of people don't get the benefits of one line modules, what would you say to those who tend to critic a lot about node modules that have just a line in them?

@daryl
Copy link

@daryl daryl commented Jul 1, 2015

I'm curious about this too, I see some modules and wonder why they're at all necessary.

This one (not by you) is a good example.

@barangutan
Copy link

@barangutan barangutan commented Jul 1, 2015

I'm also curious about this. In particular, referring to user-home as one of your examples?

@sindresorhus
Copy link
Owner

@sindresorhus sindresorhus commented Jul 1, 2015

I've been meaning to write a blog post about this, but unfortunately I'm not as productive when it comes to writing non-code.

tl;dr You make small focused modules for reusability and to make it possible to build larger more advanced things that are easier to reason about.

People get way too easily caught up in the LOC (Lines Of Code). LOC is pretty much irrelevant. It doesn't matter if the module is one line or hundreds. It's all about containing complexity. Think of node modules as lego blocks. You don't necessarily care about the details of how it's made. All you need to know is how to use the lego blocks to build your lego castle. By making small focused modules you can easily build large complex systems without having to know every single detail of how everything works. Our short term memory is finite. In addition, by having these modules as modules other people can reuse them and when a module is improved or a bug is fixed, every consumer benefits.

Imagine if PC manufacturers all made their own CPUs. Most would do it badly. The computer would be more expensive and we would have slower innovation. Instead most use Intel, ARM, etc.

This would not be possible if it weren't for how npm works. The beauty of being able to use nested dependencies means I don't have to care what dependencies a dependency I use have. That's powerful.

Some years ago. Before Node.js and npm. I had a large database of code snippets I used to copy-paste into projects when I needed it. They were small utilities that sometimes came in handy. npm is now my snippet database. Why copy-paste when you can require it and with the benefit of having a clear intent. Fixing a bug in a snippet means updating one module instead of manually fixing all the instances where the snippet is used.

For example. I have this module negative-zero. Its job is to tell me if a number is -0. Normally you wouldn't have to care about this, but it could happen. How do you figure out if a number is -0. Well easy x === 0 && 1 / x === -Infinity. Or is it? Do you really want to have to know how and why this works? I would rather require negative-zero and be productive on other things.

Another example. Chalk is one of the most popular modules on npm. What you might not realize is that it's actually a collection of modules. It depends on a module for detecting if the terminal supports color, for getting the ansi escape codes, etc. All of this could have been just embedded in the main module, and it probably is in many cases. But that would mean anyone else wanting to create an alternative terminal string styling module would have to reinvent the wheel on everything. By having these supporting modules, people can easily benefit from our work in Chalk and maybe even help improve Chalk indirectly by improving one of the dependencies.

Yet another example. I have this module user-home which get's the user's home directory. You might think it would be simpler to just do process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME. And most do this. But first, why require everyone to know how to get the home directory? Why not use a "lego block"? What you also might not realize is that this check is incomplete. On Windows you should also check process.env.HOMEDRIVE + process.env.HOMEPATH and you might also want to do additional checks. Lego blocks.

Do you make your own shoes? No, you buy them in a store. Most don't care how the shoe is made. Just how good it fits.

I want programming to be easier. Making it easier to build durable systems. And the way forward in my point of view is definitely not reinventing everything and everyone making the same stupid mistakes over and over.

@arthurvr
Copy link

@arthurvr arthurvr commented Jul 1, 2015

This is why I wish you had a blog :p I never was able to explain it that well. Good job.

@sindresorhus sindresorhus changed the title About one line node modules. About one-line node modules Jul 1, 2015
@sindresorhus sindresorhus changed the title About one-line node modules One-line node modules Jul 1, 2015
@sarbbottam
Copy link

@sarbbottam sarbbottam commented Jul 1, 2015

👍

@barangutan
Copy link

@barangutan barangutan commented Jul 1, 2015

I guess my question (and this is a legitimate question as I'm relatively new to nodejs!) was, do you see a legitimate reason for splitting user-home out from os-homedir? I mean, obviously you do as you've done it that way, but I was wondering why you just didn't just include a cached version in the original os-homedir

@bahmutov
Copy link

@bahmutov bahmutov commented Jul 1, 2015

I often find that these simple tasks, like finding user's home folder, turn out to be very non-trivial and hairy the more the code gets used. Making a separate module right away allows to properly test and fix bugs, add features etc, as new issues absolutely will arise.

@spacecowb0y
Copy link

@spacecowb0y spacecowb0y commented Jul 1, 2015

👍

@sindresorhus
Copy link
Owner

@sindresorhus sindresorhus commented Jul 1, 2015

I guess my question (and this is a legitimate question as I'm relatively new to nodejs!) was, do you see a legitimate reason for splitting user-home out from os-homedir? I mean, obviously you do as you've done it that way, but I was wondering why you just didn't just include a cached version in the original os-homedir

The user-home module was made long before os-homedir. os-homedir was made recently as a polyfill of the forth-coming built-in API in Node. I just made user-home depend on it so it will benefit from the native API. I could of course deprecate user-home now, but there are so many projects depending on it now, it really isn't worth it. I also like how in user-home it's cached by default instead of having to call it as a function. However what you're asking isn't really relevant to the small module thinking. This is just a side-effect of evolution.

@barangutan
Copy link

@barangutan barangutan commented Jul 1, 2015

Ok, thanks for the clarification! That makes a lot of sense. I just stumbled upon user-home today and my curiosity aligned perfectly with this AMA.

@sindresorhus
Copy link
Owner

@sindresorhus sindresorhus commented Jul 1, 2015

I often find that these simple tasks, like finding user's home folder, turn out to be very non-trivial and hairy the more the code gets used.

👍 Every time :p

Everyone; Keep in mind that I've failed doing the right thing hundreds of times before realizing making a separate module would be a better thing.

@noffle
Copy link

@noffle noffle commented Jul 1, 2015

Everyone; Keep in mind that I've failed doing the right thing hundreds of times before realizing making a separate module would be a better thing.

This is always helpful to hear: that one's successes are generally built upon the rubble of past failures.

@mikeal
Copy link

@mikeal mikeal commented Jul 1, 2015

Just stick this reply on Medium and call it a day :)

@addyosmani
Copy link

@addyosmani addyosmani commented Jul 1, 2015

Seriously. Copy/paste to Medium dude. There are legit insights here that wouldn't require more formatting than what you have here :)

@soutar
Copy link

@soutar soutar commented Jul 1, 2015

👍 Great explanation

@pvdz
Copy link

@pvdz pvdz commented Jul 1, 2015

My beef with one liner modules is that I still have to rely on other people doing their job well. Take for example the is-positive module posted by @daryl. I just had to file kevva/is-positive#3 against that because regardless of whether the author was even aware of the distinction, it may have been relevant for your case. If you relied blindly on that module then you may now have a bug in your code. The author was aware, apparently made aware twice, and chooses not to support this feature. Of course that's his choice. I do have opinions about that. Care.

The building block approach works well if there's a tight community that moderates these modules and puts them in a pool with the "moderator approved" logo. This may be a problem npm is facing; no quality control. Back to the jquery crap plugin era. And I still have to roll my own because existing modules do not suffice. But when asking about including trivial stuff in the framework (node) I get the standard "use npm for that". Ciiiiircles argh.

Somebody start a community moderated npm module collection site/thingie/whatchamacall it please. Quality control. How can you even write reliable node apps with out it :/

@mikeal
Copy link

@mikeal mikeal commented Jul 1, 2015

The building block approach works well if there's a tight community that moderates these modules and puts them in a pool with the "moderator approved" logo.

All the success of npm seems to point to the contrary. npm beat its early competitors because there was no gatekeeper on publishing. Node itself has enough well understood compatibility patters (error first callbacks, streams, etc) that modules can be compatible without specific coordination between the authors.

Arguments about quality seem to center around the idea that a large community has less quality than a small one. This is always true as a matter of percentage (90% of a small community might be of higher quality than all but the top 10% of a larger community) but in a large enough ecosystem this doesn't matter because 10% dwarfes all smaller curated communities.

You still have a discovery problem in large ecosystems, but that is mitigated with investment. The people I see complain most about it aren't investing in it so they can never find what they want. Instead they lock in to tightly integrated frameworks that are vertically integrated and curated, which is fine if that is your preference, but to say that these smaller curated ecosystems are of higher quality than the modules you find in a larger ecosystem once you've invested is simply untrue.

@arthurvr
Copy link

@arthurvr arthurvr commented Jul 1, 2015

The building block approach works well if there's a tight community that moderates these modules and puts them in a pool with the "moderator approved" logo. This may be a problem npm is facing; no quality control.

You kinda do the quality control yourself when choosing a module. How does the readme look? Who built this module? It it well tested? All don't guarantee anything, but are pretty good parameters to see if something is reliable. That's something that rarely happened back when there was "jQuery crap." We (as in developers) just cared if it looked good.

@mikeal
Copy link

@mikeal mikeal commented Jul 1, 2015

You kinda do the quality control yourself when choosing a module. How does the readme look? Who built this module? It it well tested?

I would add "does this do only one thing?" and "is the usage a single, simple, function?" because if all my modules fall under these guidelines my application is much easier to reason about. This also re-enforces the single line module argument.

For the record, there is one single line module I won't use and that is @visionmedia's noop module because I actually find it is better to simply do that one inline for readability (not to mention performance).

var noop = function () {}

vs.

var noop = require('noop')
@arthurvr
Copy link

@arthurvr arthurvr commented Jul 1, 2015

Yes, @mikeal, I totally agree. I was just giving some examples, the API design should definitly be an important point too. There are way too many bloated modules out there.

@pvdz
Copy link

@pvdz pvdz commented Jul 1, 2015

@mikeal; Using the success of npm as some kind of de facto counter-argument in this context seems unsound to me. There may be many reasons for it being popular; its users may not care that much about the problem I outlined.

Large vs small communities; there's another actor at play here. The 10% may dwarf the 90% but if they are so scattered and relatively unorganized, it would still not mean they outperform the small one. Think of it the Romans versus the barbarians, if that comparison makes any sense ;)

Investment.. well that may be a whole discussion on its own. One I see myself losing quickly. My main rebuttal would be that if I'm coding a node(/io/bla) app, and need some trivial functionality not exposed by this framework, I have only two choices: roll my own or scrounge npm. Both can take a lot of time because if it's cumbersome to roll my own, it'll probably be just as cumbersome to inspect and pick the right module. And still hope it didn't mess up.

Small modules that do one thing (tm) do mitigate this problem a bit, by design. But even then there are simple cases, easily overlooked, like the is-positive example earlier (oh I'm sorry to that author, I really don't mean to bash on him).

@arthurvr: while maintenance of a module as a whole (docs) is some indication, it all comes down to the code and test cases. When this matters, it comes down to a cold hard code review. And in the end it may still mean you can't use the module because it's indeed insufficient (but of course you always have that risk) or because there's a bug of sorts.

So. I didn't intend this to be a Duty Calls. It wasn't meant like that at all. It's my two cents on why I have trouble with using npm reliably. It may not be relevant for others. So I'll leave it at this.

@MaxArt2501
Copy link

@MaxArt2501 MaxArt2501 commented Jul 1, 2015

I think we should make a distinction here.
user-home is actually sweet, because it's based on a purpose: providing the user's home directory. It may be a one-liner, but it could be different in the future. Now we know it won't because Node will take care of the problem, but it's just an example.

On the other hand, negative-zero doesn't convince me. Because it's a one-liner, and it's based on something that won't change anytime soon (i.e., Javascript basic arithmetic specs).
I could use that module. But I also could define an isNegativeZero function on my own in less than 30 seconds, and use that instead. So, which is better?

Using well-known modules saves us from the hassle to fix bugs, writing unit tests and update the code to meet future specs. But for a check like that you might as well expect that there won't be any bugs ever, and no changes either, since the specs are very much frozen on the topic.
Of course, I can always make a mistake writing isNegativeZero (that would also mean that my caffeine level is dangerously low, having an impact on many other things), but it may also happen that I picked the wrong module (because I mistyped it when doing npm), or the wrong version, or even that the maintainer made a silly oversight.

Second point, time spent: my <30s function definitely took me less than the time I'd need:

  • googling if there are modules for my needs;
  • checking one or some of them, reading the docs;
  • verifying if it's actually good for my purposes with some tests;
  • including it in the project and write a concise documentation in the readme for the other members.

And I might end up with a module with an obscure name (because the best names were already taken, or because the author doesn't speak English well, or is just an evil mastermind) instead of my well-named isNegativeZero function that needs no explanation.
Not to mention the time the other members must spend to get to know the new module.

But, above all, what would worry me the most is the proliferation of dependencies. A package.json file for a common medium-sized project usually include dozens of dependencies. What's worse, we currently have no way to group them in scope categories: they're all there, sort of "globally defined" modules (relative to the project, of course).
If we lower the complexity of the modules we know down to the most basic things, the list of dependencies might grow exponentially. This can be a problem for projects with many active members, that might end up knowing only a part of the modules used and ignoring the others. Imagine the scenario: "Dude, I require'd this xxx-yy for that, why didn't you use it/did you install yyy-xx instead?" "Sorry, I didn't know/I had no idea you used that/was another module really necessary?"
It's a matter of team coordination.

Oh, but you can say: "Well, if we don't like a big pool of modules, I can make a module that collects all the modules in the same area. Let's start with some math-utils.js, which requires negative-zero, and is-positive, and..."
And you stop, realizing that if you're forced to make a module to organize your one-liner modules, you might as well write the one-liners by yourself in your module.

So, in the end, we all like modules, we like them to no end especially if they're well made packages, with docs, tests, comments, changelogs, semver support and all, but the risk of abusing this system is concrete and should be considered. The control of the components of the probject and thus the coordination of the team might suffer.

Probably what we really need is new tools to handle our modules. Quality control tools, above all.

@mattdesl
Copy link

@mattdesl mattdesl commented Jul 1, 2015

@qfox Sometimes you will find modules that do almost exactly what you need, but not quite exactly. In these instances, you might be able to nudge the author to improve/document/change/etc the module. Or, you can spend 5 minutes to write your own module, e.g. is-positive-or-zero and be done with it. After which, anyone else stumbling along the same problem as you will now have a clear path ahead of them.

When you embrace the "small module" philosophy you should also be ready to get your own feet wet, and publish your own modules as needed. 😄 Most of the people advocating for small modules (substack, mikolalysenko, hughsk, sindresorhus, etc) are also writing a lot of their own.

@mattdesl
Copy link

@mattdesl mattdesl commented Jul 1, 2015

@MaxArt2501

But I also could define an isNegativeZero function on my own in less than 30 seconds, and use that instead. So, which is better?

Do people really know how to write this function by heart? It is not something I ever hope to memorize or be quizzed on, and probably not something I could pull out of thin air in under 30 seconds.

x === 0 && 1 / x === -Infinity

Either way; once you know the module exists and is good, it will almost always be faster (and safer) to just npm install it. 😄

@MaxArt2501
Copy link

@MaxArt2501 MaxArt2501 commented Jul 1, 2015

@mattdesl

Do people really know how to write this function by heart?

Huh, maybe it's just me, but I suppose they do. Once you get to know that in Javascript there is a "negative zero" (and that's the shocking part), the next question should logically be "how can I tell a positive from a negative zero if they bot === 0?" and the answer is "if you divide a positive number by -0, you get -Infinity."

But it's just an example. Consider the following as an alternative:

module.exports = exports = function isSameDay(date1, date2) {
    return date1.getFullYear() === date2.getFullYear()
        && date1.getMonth() === date2.getMonth()
        && date1.getDate() === date2.getDate();
};

Either way; once you know the module exists and is good, it will almost always be faster (and safer) to just npm install it. 😄

Sure! Once you know it. And if you're alone in the project and never have to write docs about its introduction and all that boring yadda-yadda.
Otherwise, copy&pasting a function and maybe writing a small docblock for it might be faster.

Also, to be honest, I already have to memorize a ton of language specs. Remembering a plethora of micro-modules for every small thing would be another burden on my ever-waning memory.

@samarpanda
Copy link

@samarpanda samarpanda commented Jul 19, 2015

@hemanth Great question, @sindresorhus Fantastic answer. 👍 Awesome 💯

@halhenke
Copy link

@halhenke halhenke commented Aug 26, 2015

Late unwanted perspective:

I think theres no hard and fast rule that I can think of to answer the question. Generally i think modularity has huge advantages but there is a point where it potentially becomes unhelpful. People have voiced reasons concerning the friction of discovering and vetting packages. I also think that sometimes when we refactor code with the goal of removing complexity we actually just end up shuffling complexity into a different part of your application. I dont think anyone wants to look at a package.json file with 1000 top level dependencies and try to figure out whats going on. Obviously thats an extreme scenario but I think it illustrates the idea that the returns from maximising modularity can diminish to the point where they are exceeded by the accumulated minor pains associated with managing all these dependencies.

Also the thought of what a new npm package name might look like in 5 years time is vaguely terrifying.

@janeluck
Copy link

@janeluck janeluck commented Nov 16, 2015

The power of Organizational Resources

@deanius
Copy link

@deanius deanius commented Dec 22, 2015

Very true and appealing, but there are definitely costs to module creation and maintenance you don't mention.
Here is the rundown of the line count for your 'one-line' negative-zero module. (Edit: And I see you don't have a .travis.yml either so add some lines for doing that, and some time for checking that out.)

wc *
       4      18      89 index.js
      21     172    1119 license
      32      57     559 package.json
      36      56     624 readme.md
      12      20     259 test.js
     105     323    2650 total

Some costs can be alleviated with good tools (e.g. yeoman generator for modules); others like crafting tests and Readmes cannot. I'd love a world where people did what you said- but the real and percieved costs mean that not doing so is rational in many cases.

@sindresorhus
Copy link
Owner

@sindresorhus sindresorhus commented Dec 22, 2015

There's indeed a slight overhead doing it this way, but I think the benefits outweigh that.

others like crafting tests and Readmes cannot

You'd hopefully be writing tests and docs for the code anyways.

@bahmutov
Copy link

@bahmutov bahmutov commented Dec 22, 2015

Plus one can target what files are published into NPM package using "files" or ".gitignore" or ".npmignore" settings, see http://glebbahmutov.com/blog/smaller-published-NPM-modules/ - the strict boundaries among the packages are really worth the slight increase in size, in my opinion

@halhenke
Copy link

@halhenke halhenke commented Dec 30, 2015

Just as a thought - perhaps this sort of stuff might be more useful at a community level if it was part of a larger library of tools - something like a micro-lodash?

The primary benefit would be one of discoverability/visibility - people would know where to look if they wanted to check if a oneliner existed for their particular use case and over time people would become more and more familiar with what was availabe. Then of course the individual one liners could be available as individual packages much as lodash functions are today.

While "programming via require/import" seems a little weird to me at first thought, there are benefits as mentioned around testability etc. The more I think about it the more a oneliners library seems like it could be pretty cool.

@sindresorhus
Copy link
Owner

@sindresorhus sindresorhus commented Oct 25, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.