Watch this video. I really like a lot of what he has to say. Two main comments:
URIErroris completely unnecessary, and
EvalErroris always better off being expressed as something else. The only Error types you ever need to use in your code are
TypeError. That being said, “Don’t create a new exception type” is 100% spot-on. Even having typed catches in the language encourages this kind of awful profusion.
- The reliance on “the standard library” is, imo, a bad thing. The idea that we should not reinvent wheels is great. s/standard library/npm registry/g and you’ve got me.
Many aspects of the Node.js module system, and the npm package registry, are built with exactly these principles in mind.
module.exportsallows you to export a single thing from a module. If I had the opportunity to do it again, I’d only support that, rather than an
exportsobject. If you really want to export multiple things, you’d still be able to, but it should be an extra mental leap to do so.
- The single-level namespace in the npm registry makes it a bit less likely to end up with garbage like
com.initech.utils.UtilityFactory.UtilityFactoryExceptions.UtilityFactoryNotFoundException. “Namespaces are for preventing collisions, not for creating taxonomies.” I cheered when he said this.
- Once upon a time, I foolishly implemented the proposed “modules” feature in package.json. Ryan gave me a lot of shit about that and refused to support it in node, no matter how easy it was to do. I realized (a bit too late) that he was absolutely right, and pissed off some folks by removing it. Today, you get one
mainmodule defined in package.json. That’s it. A package is a module. Yes, a package is also a folder, and modules are files, so you can dive into it and get at its guts. But it’ll feel off, because it’s weird, and non-standard. You’ll get funny looks. This is on purpose, because it nudges you towards a flatter namespace by default.
The social engineering in node and npm is always more carrot than stick, and we always try to nudge people in a good direction by saying “no” to a feature rather than saying “yes” to one. This hasn’t been done perfectly in the past, but I am fairly proud of the results.
It’s a shock to someone who’s used to writing giant monolithic programs, and has to re-think how they draw their boundaries. That’s good. In fairly short order, things like local installs and limited exports start to “click” in a programmers mind most of the time. I’ve seen a lot of people start out frustrated and angry, and then a month later tell me how odd it is to use other platforms that don’t have the a similar approach.
When you solve a problem that others accept as the status quo, you often end up finding new problems. You don’t have to worry about coming up with unique descriptive names for your programs when you can nest them 7 layers deep underneath a company name. You don’t need to come up with approaches for managing a deep dependency graph if conflicts are insoluble.
It’s not that I’m against using classes. I’ve written several programs that use classes extensively. It’s the proper abstraction for a long-lived object that needs to keep state and be interacted with. The hoop-jumping required to do this sort of thing in pure functional languages, with accumulator sets and so on, is kind of silly sometimes.
That being said, the point of using classes is to make things easier. If your class is a fire-and-forget thing with a single method, then it’s really just a function, and would be easier expressed in that manner.
There’s no hard and fast rule here. You have to actually think about what would make your program easier to use, easier to debug, and easier to understand.