Node.js has a higher cognitive load than Java

Graham Cox on May 19, 2018

I'm going to preface this by saying that I'm a much bigger user of Java than I am of node. I do like node, but I just don't use it as much. I'm ... [Read Full]
markdown guide
 

And I know that's going to be a hugely unpopular opinion

It's interesting that this is a controversial opinion. Javascript is a language with multiple competing module systems (with the official module system not even available in most runtimes), a nearly non-existent standard library, and a multitude of confusing semantics (variable hoisting, this binding, implicit conversions).

The highly dynamic nature of javascript means static analysis is imprecise, which affects tooling. Large applications need to use bundlers which have a massive learning curve of their own. In order to leverage modern features we often need to run our code through a transpiler that fills in missing functionality. The built in date and time functionality is based on a flawed java implementation circa 1995. It's a tricky language to optimize and there are a number of considerations that need to be taken into account in order to prevent deoptimization.

I could keep going on, but I think the point is clear: Javascript the language is objectively bad. Nobody, given sufficient time and forethought would create a language like javascript in its current form. It is impressive for a lot of other reasons, namely how it came to be and how much effort has gone into making it reasonably usable, but that's a consequence of it being ubiquitous and the most legitimate "write once run everywhere" runtime.

 

The thing is, JavaScript has a reputation for being used for small, simple things. Whereas Java is better known for large scale enterprise systems. And given the comparison, you immediately will think that JavaScript is simpler to get going with than Java...

 

Well, if by small, simple things you mean, "doesn't have tests, complex dependencies, or build steps like transpiling," then yeah ;)

 

This feels like more of a build tool issue than a language issue.

I'll grant you: maven does a lot of stuff out of the box with very little configuration. The JS environment is definitely behind the curve here and a lot of that, in my opinion, is due to the vast array of options over the years (grunt, gulp, webpack, etc)

However, maven can also be a freaking nightmare once you have to deal with competing dependencies or if dependencies contain overlays.

None of this really contributes to the cognitive load of the language; rather, I think this speaks to the fact that the JS ecosystem hasn't yet reached the level of standardization that one experiences when dealing with Java.

I also think that the Java ecosystem has had the benefit of the attention of engineers with experience building large systems for longer and so have felt (and dealt with) the pain points that these entail.

 

I absolutely agree. However, for most people the build tool is their first interaction with a new project. And for the developers the build system is their day-to-day interaction. So a build system that makes life easy is hugely beneficial.

And then there's the fact that the build system is generally tied to the language. (Ignoring things like make...)

 

I tend to agree. Although I have almost no Java experience, I can say that it's not all that hard to achieve the kind of all in one build process you're talking about in Node.js using NPM scripts and module loaders like webpack.

 

So - and I'm seriously interested here - how do you achieve:

  • Build all of the code
  • Run all of the unit tests
  • Run all of the Integration tests
    • Start up a temporary Postgres Database running the correct version
    • Set up the correct schema in the database
    • Start up the service being tested
    • Run all of the tests
    • Shut everything down

The Integration Tests part of that in particular I've always struggled with. The best I've been able to come up with is using Docker and Docker-Compose, but not only is that a bit flakey - getting the startup ordering right is non-trivial - but it's yet another tool for developers to need in order to run the full stack tests.

There are many ways to achieve this, and it does involve some initial work on the part of the developer, but once everything is setup, it is a thing of pure beauty. I'll give you an example of the build process for a small framework I'm working on, it might not touch on all the points you mention, but it should be enough to make you a (at least partial) believer. If you want to take a deeper look, check it out here.

So, with one command, git push here is what happens:

  • Runs all unit tests using jest
  • Transpiles TypeScript to JavaScript
  • Bundles up the code using rollup
  • Automatically generates API documentation using ts-doc
  • Pushes commits to the remote (github)
  • Starts up a build on Travis CI where it will:
    • Install all dependencies
    • Run all unit tests
    • Transpile TypeScript to JavaScript
    • Bundle up the code using rollup
    • Automatically generate API documentation using ts-doc
    • Send test coverage info to coveralls
    • Push API documentation to GitHub pages
    • Analyze the commits to see if there are changes that warrant a patch, minor or major version release.
    • If there is, modify package.json file to include new version number.
    • Push new release to NPM
    • Add release information to CHANGELOG.md
    • Commit the new package.json and CHANGELOG.md to GitHub remote.

Of course, your build process would not need many of these steps and would add some that aren't there. But the point is that, with one command, all these automated steps were triggered and you can make that happen too.

How? It all depends on the tools, libraries, services and environments that you use. But the basis for all this is NPM scripts, that you'll normally find in the package.json file. You might have to write a few custom Node scripts to do some fancier stuff but it's not all that complicated and is well worth it to automate the entire process. Hope this helps.

So, without wanting to sound flippant, what's the one command that someone who has only just checked out the repository can run to achieve all of that? And the one command that can be run before a commit - to make sure that I've not introduced any breaking changes in the commit I'm about to do?

It looks like it's npm run prepush, but it's not obvious to me.

And this is exactly my point. It's not obvious, because there is so much flexibility and so many different ways to achieve things.

In the Java/Maven world, I know that I can run mvn install on any project to run the complete build/test/etc lifecycle. This one command will:

  • Download all of the dependencies
  • Run all static checks
  • Perform any code generation steps needed
  • Perform any resource generation steps needed
  • Compile all of the code - main and test
  • Package everything up correctly
  • Run all of the tests - unit and integration

Additionally, if I ran mvn site then it will build the full set of documentation for the project. And if I instead ran mvn deploy then it will deploy the output to the configured destination - e.g. putting artifacts into Archiva, sending the actual application to Heroku, sending the built site to S3, etc.

And all of this works exactly the same on a single module or across a multiple-module build.

And the Integration tests can include steps to start and stop dependant services if needed - e.g. docker containers, the actual service under test, etc.

And packaging everything up can include into WAR files, executable JAR files, Docker containers, whatever is needed.

But the key thing is - I don't have to think about it. As someone new to the project, I already know how to work with it. As someone doing day-to-day work with it, it's easy.

(And, before you say, I know a bunch of that is because it's what I know, and that if I was more used to Node, or Python, or something like that then this would probably seem overly complex and that language would seem simpler.)

Super interesting. Like I said, I have next to no practical experience with Java, and the Java ecosystem, so it looks like I misunderstood exactly what you meant. What you are describing is very cool. I guess you summed it up nicely when you said:

there is so much flexibility and so many different ways to achieve things.

And that is a problem in the JavaScript universe, not only with build processes, but with pretty much every aspect of coding and development. It's the whole convention vs. configuration thing and in the JavaScript world, we are big time in the configuration side of things. There are so many ways to do everything. JavaScript fatigue is a real thing.

So you definitely have a point; you can't expect that running npm build on one JS project, will have the same impact as running npm build on another JS project. There is no convention when it comes to that.

 

We are doing the common Angular FE/ Java Backend. From the start, it amazes me all the time how complicated it is to get a working environment running, installing all the right versions an so on. Now we build with maven-frontend-plugin which at least downloads the correct node/npm versions and then runs the build.

 

maven-frontend-plugin is awesome. I love it for combined builds. As is the docker-maven-plugin from Spotify, and the postgresql-embedded dependency from Yandex. Between those, and a well set up Pom file, the only thing that I ever have to worry about version wise is the JVM and Maven versions - everything else is set in stone.

What's more, using that stack, the only versioning concern at all is that the live database matches the one in the tests. I can build Docker containers of the actual services that contain the exact JVM to run on, so even that isn't a concern.

 

I am inclined to agree with you that getting from 0 to prototype is easier in Java than it is node.js. This is also true of Ruby, PHP and Python, by the way, where things like Rails, Laravel, and Django give you a platforms with great scaffolding abilities.

However, I think node is closing ground here fast. What it has lacked heretofore is an opinionated framework such as Rails that lets you set up a lot of scaffolding and boilerplating fast, precisely because that opinionated philosophy takes away a lot of choices and makes automating this easier.

But that opinionated framework comes with drawbacks. It's a lot harder to optimize your apps because the frameworks optimize for development time, not performance. It's a pain to do weird/custom things, like if you want some highly optimized SQL, have fun fighting with your ORM.

Furthermore, this kind of thing will come to Node.js too -- javascript is too popular for it not to. Meteor is something you might want to check out, and yeoman probably already has a lot of what you are looking for. A lot of times yeoman drives me a little nuts because it seems like every generator template has about 80% of what I want and none are perfect, but YMMV.

 

I'd argue the cross platform support is a bit moot at this point. With Windows 10 supporting a native Ubuntu vm, nobody has a reason not to have a bash terminal on their machine. Best practice from my experience has been to have a bash scripts folder and a set of npm commands to instantiate environments. I tend to WANT my codebases built separately, preferably through CI and and deployed to a container repo. Or even better, full serverless. The Serverless framework has some pretty fantastic offline support for testing, and it's a breeze to deploy from there (handles teardown as well). It's two commands instead of one, to do a docker compose and a serverless-offline startup, but it gives you a remarkable level of flexibility in your stack. Combine that with something like chakram for testing and you have some robust end to end tests in a repeatable environment.

 

So, at my day job, Windows 10 isn't supported yet. We've got people on Windows 7 and Windows 8, but the security and IT people have yet to officially roll out Windows 10 to anyone. And I know of other, bigger organisations where Windows 8 isn't even supported yet!

Just because the latest and greatest version of the OS supports these things doesn't mean everyone is using it. And if I can do things that make life easier for people in general, I don't see any reason not to.

 

I haven't made a full production app in Java, but have done that with Django and another one with Node. And testing Node app is a disaster. Django tests are pretty easy to write and it handles all the database setup and teardown easily. I can write tests in nodejs too, but I have to mock out the whole database interaction which is a big task in itself and then cross my fingers and rely on the fact that database interaction will work as expected.
I think Java and some languages like PHP and Ruby have these frameworks which work so awesomely for big apps and NodeJS still lacks those.

 

I think you like writing more lines of code for simple things

 

This argument is totally broken.

The headline is prototypical for that. While Java is a language, nodejs is not.

Another highlight: When talking about the good things in Java, you are talking about a built tool written in Java. We all should know that Java as well as Javascript are turing complete. So that it should be valid to say: when I am able to solve a problem in Java, I should be able to solve that problem in Javascript. So when there is Maven for Java the only reason, that it doesn't exist in Javascript is, that nobody has written a port yet.

Then: when talking about the goodness of Java you talk about many things completely unrelated to the language.

Finally what you should have written is:
The Javascript ecosystem lacks mavenlike tooling which let's me work with comparable workflows.
Or if you want it a bit more clickbaity: Java tooling is more mature than Javascript tooling - which opens up a discussion.

code of conduct - report abuse