Home > News content

After leaving from Sun, I "abandoned" Java and embraced JavaScript and Node.

via:博客园     time:2018/9/8 23:33:32     readed:609

English original text:Why is a Java guy so excited about Node.js and JavaScript?

I am a member of the former Sun Java SE team. After working for more than 10 years, —— January 2009—— that is, before Oracle acquired Sun, I left the company and then fell in love with Node. .js.

How much am I obsessed with Node.js? Since 2010, I have written extensively on Node.js programming, published four books related to Node.js development, and other books and tutorials related to Node.js programming.

During my time at Sun, I believe that Java is everything. I spoke at JavaONE, co-developed the java.awt.Robot class, organized the Mustang Return Race (Java 1.6 version of the vulnerability discovery contest), and helped launch the "Java Release License", which was later launched in the OpenJDK project. Has played a certain role. I wrote one or two blog posts a week on java.net (this site is now dissolved) to discuss the main events happening in the Java ecosystem and persisted for 6 years. The main theme of these blog posts is about "defending" Java, because there is always someone prophesying Java's "death".

In this article, I will explain how my Java diehardness became a Node.js and JavaScript diehard.

But in fact, I am not completely out of Java. In the past three years, I have written a lot of Java/Spring/Hibernate code. But the two-year Spring coding experience made me understand the truth: hiding complexity doesn't bring simplicity, it only creates more complexity.

Java has become a burden, and Node.js programming is full of fun.

Some tools are the result of years of tempering and refining by designers. They try different ideas, remove unnecessary attributes, and end up with a tool with just the right attributes. The simplicity of these tools is even amazing, but Java obviously doesn't fall into this category.

Spring is a very popular framework for developing Java web applications. The core purpose of Spring (especially Spring Boot) is to be an easy-to-use pre-configured Java EE stack. Spring programmers don't need direct access to servlets, data persistence, and application servers to get a complete system. The Spring framework handles all of these details, and you only need to focus on the business coding. For example, the JPA Repository class is "findUserByFirstName" method synthetic database query —— you don't need to write any query code, just name the method in a specific way and add it to the Repository, Spring will be responsible for processing the rest .

This was originally a great story, a good experience, but it was not.

When you encounter Hibernate's PersistentObjectException, do you know what went wrong? It may take a few days for you to find the problem. The cause of this exception is that the JSON message sent to the REST endpoint has an ID field. Hibernate wants to control the ID value itself, so it throws this confusing exception. Look, this is the result of oversimplification. In addition to this, there are thousands of other equally confusing anomalies. In the Spring stack, one subsystem wraps around another, waiting for you to make a mistake, and then throwing an application crash exception to punish you.

Then, you'll see the full-screen stack trace, which is full of abstract methods like this. Faced with this level of abstraction, it is clear that more logic is needed to find what you want. So much stack trace information isn't necessarily bad, but it's also reminding us: How much is this memory and performance overhead?

And how is the zero code "findUserByFirstName" method executed? The Spring framework must parse the method name, guess the programmer's intent, construct something like an abstract syntax tree, and generate some SQL statements …… So how much overhead does it take to complete the process?

After going through this process over and over again, after spending a lot of time learning what you shouldn't have learned, you might come to the same conclusion: hiding complexity doesn't bring simplicity, it only produces more More complexity.

The other side is Node.js

Spring and Java EE are very complex, and Node.js is a clean stream. The first is the design aesthetics that Ryan Dahl applies to the core Node.js platform. He pursued something different and spent years honing and improving a series of core Node.js design ideals, resulting in a lightweight single-threaded system. It cleverly utilizes JavaScript anonymous functions for asynchronous callbacks and becomes a runtime library that implements asynchronous mechanisms.

Then there is the JavaScript language itself. JavaScript programmers seem to prefer codeless templates so their intent can work.

We can illustrate the difference between Java and JavaScript by implementing an example of a listener. In Java, listeners need to implement abstract interfaces, and you need to specify a lot of details. These cumbersome templates of programmers' intentions are gradually overwhelming.

In JavaScript, you can use the simplest anonymous function —— closure. You don't need to implement any abstract interfaces, just write the code you need, and there are no extra templates.

Most programming languages ​​try to cover up the programmer's intent, which makes understanding the code more difficult.

But there is one thing to note in Node.js: callback hell.

No perfect solution

In JavaScript, we have struggled to solve two asynchronous-related problems. One is what Node.js calls "“ callback hell". It's easy to fall into the trap of deep nested callback functions, each of which complicates the code, making it more difficult to handle errors and results. But the JavaScript language does not provide programmers with a way to correctly express asynchronous execution.

As a result, some third-party libraries have emerged that promise to simplify asynchronous execution. This is another example of bringing more complexity by hiding complexity.

Const async = require (‘async’);
Const fs = require (‘fs’);
Const cat = function (filez, fini) {
  async.eachSeries (filez, function (filenm, next) {
    fs.readFile (filenm, ‘utf8’, function (err, data) {
      If (err) return next (err);
      Process.stdout.write (data, ‘utf8’, function (err) {
        If (err) next (err);
        Else next ();
      });
    });
  },
  Function (err) {
    If (err) fini (err);
    Else fini ();
  });
};
Cat (process.argv.slice (2), function (err) {
  If (err) console.error (err.stack);
});

This is an example that mimics the Unix cat command. The async library is ideal for simplifying the asynchronous execution order, but it also introduces a bunch of template code that obscures the programmer's intent.

This actually contains a loop, just without using loop statements and natural loop constructs. In addition, the processing logic for errors and results is placed inside the callback function. We can only do this before Node.js adopts ES 2015 and ES 2016.

In Node.js 10.x, the equivalent code looks like this:

Const fs = require (‘fs’) .promises;
Async function cat (filenmz) {
  For (var filenm of filenmz) {
    Let data = await fs.readFile (filenm, ‘utf8’);
    Await new Promise ((resolve, reject) => {
      Process.stdout.write (data, ‘utf8’, (err) => {
        If (err) reject (err);
        Else resolve ();
      });
    });
  }
}
Cat (process.argv.slice (2)).catch(err => {
    Console.error (err.stack);
});

This code rewrites the previous logic using the async/await function. Although the asynchronous logic is the same, this time a normal loop structure is used. The handling of errors and results is also natural. Such code is easier to read and easier to code, and the programmer's intent is easier to understand.

The only trick is that process.stdout.write does not provide a Promise interface, so it needs to be wrapped in Promise when used in an asynchronous function.

The problem of callback hell is not solved by hiding complexity. Instead, it is the evolution of language and paradigm that solves this problem. By using the async function, our code becomes more beautiful.

Improve clarity with well-defined types and interfaces

When I was still a diehard of Java, I firmly believed that strict type checking was a blessing for developing large applications. At that time, the concept of microservices did not appear, and there was no Docker. People developed single applications. Because Java has strict type checking, the Java compiler can help you avoid many errors —— that is, it prevents you from compiling the wrong code.

In contrast, the type of JavaScript is loose. The programmer is not sure what type of object they are receiving, so how do programmers know how to handle this object?

However, Java's strict type checking also resulted in a lot of boilerplate code. Programmers often need to do type conversions or otherwise ensure everything is accurate. It takes a lot of time for the programmer to make sure the type is accurate, so use more boilerplate code and hope to save time by capturing and fixing errors early.

Programmers have to use complex large IDEs, and using a simple editor is not enough. The IDE provides Java programmers with drop-down lists that display the available fields of the class, describe the parameters of the methods, help them build new classes, and refactor.

Then you have to use Maven……

In JavaScript, you don't need to declare the type of a variable, so you don't usually need to do a type conversion. Therefore, the code is easier to read, but uncompiled errors may occur.

This will make you prefer Java or hate Java, depending on yourself. Ten years ago, I thought that Java's type system deserves extra time because it gives us more certainty. But today, I think it's too expensive, and using JavaScript is much simpler.

Use a small module that is easy to test to remove bugs

Node.js encourages programmers to divide programs into small units, which are modules. Although the module is small, it can solve the problem just mentioned to some extent.

A module should have the following characteristics:

  • Self-contained —— package the relevant code into a unit;
  • Strong border —— code inside the module prevents external code from intruding;
  • Explicitly export —— By default, the data in the code and module is not exported, only the selected functions and data are exposed to the outside;
  • Explicitly import —— declare which modules they depend on;
  • May be independent —— can publicly publish modules to npm repositories or other private repositories for easy sharing between applications;
  • Easy to understand —— less code means easier to understand the purpose of the module;
  • Easy to test —— small modules make unit testing easy.

All of these features are combined to make the Node.js module easier to test and have a well-defined scope.

The fear of JavaScript stems from its lack of strict type checking, so it can easily lead to errors. However, in modules with clear boundaries, the affected code is limited to the inside of the module. Therefore, most problems are safely hidden within the boundaries of the module.

Another solution to the loose type problem is to do more testing.

You have to spend some of the time saved (because it's easier to write JavaScript code) for testing. Your test case must catch errors that the compiler might catch.

For those who want to use static check types in JavaScript, consider using TypeScript. I didn't use TypeScript, but I heard that it is very good. It is compatible with JavaScript and provides useful type checking and other features.

But our focus is on Node.js and JavaScript.

Package management

When I think of Maven, I am big. It is said that a person either loves it or despise it, without a third choice.

The problem is that there is no core package management system in the Java ecosystem. Maven and Gradle are actually great, but they are not as useful, usable, and powerful as Node.js's package management system.

In the Node.js world, there are two excellent package management systems, starting with the npm and npm repositories.

With npm, we have a good model for describing package dependencies. Dependencies can be strict (specify specific versions), or use wildcards to indicate the latest version. The Node.js community has released hundreds of thousands of packages to the npm repository.

Not just Node.js engineers, front-end engineers can use the npm repository. Previously they used Bower, and Bower is now deprecated, and they can now find all available front-end JavaScript libraries in the npm repository. Many front-end frameworks, such as the Vue.js CLI and Webpack, are based on Node.js.

Another package management system for Node.js is yarn, which also pulls the package from the npm repository and uses the same configuration file as npm. The main advantage of yarn runs faster.

performance

Once upon a time, both Java and JavaScript were blamed for their slowness.

They all need to be converted by the compiler into source code that is executed by the virtual machine. Virtual machines typically further compile bytecode into native code and use various optimization techniques.

Both Java and JavaScript have a lot of motivation to make the code run faster. In Java and Node.js, the motivation is to make server-side code run faster. On the browser side, the motivation is to get better client application performance.

Oracle's JDK uses HotSpot, a super virtual machine with multiple bytecode compilation strategies. HotSpot is highly optimized to generate very fast code.

As for JavaScript, we can't help but wonder: How can we expect JavaScript code running in a browser to implement complex applications? Implementing an office document processing suite based on browser JavaScript seems to be impossible. It’s a horse, and when you pull it out, you know. This article was written by Google Docs and it performs very well. The performance of browser-side JavaScript is skyrocketing every year.

Node.js directly benefits from this trend because it uses Chrome's V8 engine.

Below is a video link to Peter Marshall's presentation. He is an engineer at Google and is responsible for the performance enhancements of the V8 engine. In the video he describes why the V8 engine replaced the Crankshaft virtual machine with a Turbofan virtual machine.

High-performance JavaScript in the V8 engine:https://youtu.be/YqOhBezMx1o

In the field of machine learning, data scientists often use R or Python because they rely heavily on fast numerical calculations. But for a variety of reasons, JavaScript performs poorly in this regard. However, someone is developing a standard JavaScript library for numerical calculations.

Numerical calculations in JavaScript:https://youtu.be/1ORaKEzlnys

Another video demonstrates how to use TensorFlow in JavaScript with TensorFlow.js. It provides an API similar to TensorFlow Python that can be imported into pre-trained models. It runs in a browser and can be used to analyze real-time video to identify trained objects.

JavaScript-based machine learning:https://youtu.be/YB-kfeNIPCE

In another presentation, IBM's Chris Bailey discusses the performance and scalability issues of Node.js, especially in terms of Docker/Kubernetes deployment. Starting with a set of benchmarks, he demonstrated that Node.js far surpasses Spring Boot in terms of I/O throughput, application startup time, and memory footprint. In addition, thanks to improvements in the V8 engine, each new release of Node.js has a significant performance improvement.

Node.js performance and highly scalable microservices:https://youtu.be/Fbhhc4jtGW4

In the video above, Bailey said that we should not run computationally intensive code in Node.js. Because Node.js uses a single-threaded model, running computationally intensive tasks for long periods of time can cause event blocking.

If JavaScript improvements don't meet your application's requirements, there are two other ways to integrate native code directly into Node.js. The most straightforward way is to use the Node.js native code module. Node-gyp is included in the Node.js toolchain and can be used to handle links to native code modules. The video below demonstrates how to integrate the Rust library with Node.js:

JavaScript is integrated with Rust, much simpler than you think:https://youtu.be/Pfbw4YPrwf4

WebAssembly can compile other languages ​​into a very fast JavaScript subset. WebAssembly is a portable format for executable code that runs inside the JavaScript engine. The video below provides a good overview and demonstrates how to use WebAssembly to run code in Node.js.

Use WebAssembly in NodeJS:https://youtu.be/hYrg3GNn1As

Rich Internet Application (RIA)

Ten years ago, the software industry has been hot with the use of a fast JavaScript engine to implement rich Internet applications, replacing desktop applications.

This story actually started more than twenty years ago. Sun and Netscape have reached a consensus to use Java applets (Applets) in Netscape Navigator. The JavaScript language was developed to some extent as a scripting language for Java applets. There is a Java Servlet on the server side and a Java Applet on the client side so that the same programming language can be used on both ends. However, for various reasons, this wonderful wish has not been realized.

Ten years ago, JavaScript began to be powerful enough to implement complex applications. Therefore, RIA is considered the terminator of Java client applications.

Today, we are beginning to see the idea of ​​RIA being realized. The server-side Node.js and JavaScript on both sides make this possible.

Of course, the demise of Java as a desktop application platform is not due to JavaScript RIA, but because Sun ignores client-side technology. Sun is focusing on enterprise customers who demand fast server-side performance. I was still working at Sun, and I saw this happening. What really kills Applets is a security hole discovered several years ago in the Java plug-in and Java Web Start. This vulnerability led to a global unanimous call for stopping Java Applet and Java Web Start applications.

We can still develop other types of Java desktop applications, and the competition between NetBeans and the Eclipse IDE still exists. However, Java's work in this area is stagnant, and there are very few Java-based applications other than development tools.

JavaFX is an exception.

10 years ago, JavaFX intended to be Sun's counterattack on the iPhone. It is used to develop Java-based mobile GUI applications and wants to smash Flash and iOS apps. However, this has not happened. JavaFX is still available now, but it didn't have the original flaws.

All the excitement in this area takes place in React, Vue.js, and similar frameworks, and JavaScript and Node.js benefit to a large extent.

in conclusion

There are many options for developing server-side applications. We are no longer limited to the languages ​​that begin with "Pl", Perl, PHP, Python, and Java. We also have Node.js, Ruby, Haskell, Go, Rust, and more.

As for why I turned to Node.js, it's clear that I prefer the feeling of freedom when programming with Node.js. Java has become a burden, and Node.js has no such burden. If I pick up Java again, it must be because someone paid for it.

Every application has its real needs. It’s not always right to use Node.js just because you like it. When choosing a language or a framework, there are always technical considerations. For example, some of the work I've done recently involves XBRL documentation, and since the best XBRL libraries are implemented in Python, it's necessary to learn Python.

China IT News APP

Download China IT News APP

Please rate this news

The average score will be displayed after you score.

Post comment

Do not see clearly? Click for a new code.

User comments