有域名建网站,百度搜索指数排行榜,自定义颜色 网站,网站建设系统开发感想与收获我是前Sun公司Java SE团队的一名成员#xff0c;在工作了10多年之后——2009年1月——也就是在甲骨文收购Sun公司之前#xff0c;我离开了公司#xff0c;然后迷上了Node.js.
我对Node.js的痴迷到了怎样的程度#xff1f;自2010年以来#xff0c;我撰写了大量有关Node.js…我是前Sun公司Java SE团队的一名成员在工作了10多年之后——2009年1月——也就是在甲骨文收购Sun公司之前我离开了公司然后迷上了Node.js.
我对Node.js的痴迷到了怎样的程度自2010年以来我撰写了大量有关Node.js编程的文章出版了四本与Node.js开发有关的书籍以及与Node.js编程有关的其他书籍和众多教程。
在Sun公司工作期间我相信Java就是一切。我在JavaONE上发表演讲共同开发了java.awt.Robot类组织Mustang回归竞赛Java 1.6版本的漏洞发现竞赛协助推出了“Java发行许可”这在后来的OpenJDK项目启动过程中起到了一定的作用。我在java.net这个网站现已解散上每周写一到两篇博文讨论Java生态系统中所发生的主要事件并坚持了6年。这些博文的主要主题是关于“保卫”Java因为总有人在预言Java的“死期”。
在这篇文章中我将会解释我这个Java死忠是如何变成一个Node.js和JavaScript死忠的。
但其实我并没有完全脱离Java。在过去的三年中我编写了大量Java/Spring/Hibernate代码。但两年的Spring编码经历让我明白了一个道理隐藏复杂性并不会带来简单性它只会产生更多的复杂性。
Java已成为一种负担Node.js编程却充满了乐趣
有些工具是设计师花费数年磨砺和精炼的结果。他们尝试不同的想法去掉不必要的属性最终得到一个只带有恰到好处属性的工具。这些工具的简洁性甚至达到让人感到惊艳的程度但Java显然不属于这一类。
Spring是一个非常流行的用于开发Java Web应用程序的框架。Spring特别是Spring Boot的核心目的是成为一个易于使用的预配置的Java EE栈。Spring程序员不需要直接接触Servlet、数据持久化、应用程序服务器就可以获得一个完整的系统。Spring框架负责处理所有这些细节你只需要把精力放在业务编码上。例如JPA Repository类为“findUserByFirstName”方法合成数据库查询——你不需要编写任何查询代码只需按照特定方式给方法命名并添加到Repository中即可Spring将负责处理其余的部分。
这原本是一个伟大的故事一种很好的体验但其实并不然。
当你遇到Hibernate的PersistentObjectException时你知道是哪里出了问题吗你可能花了几天时间才找到问题所在导致这个异常的原因是发给REST端点的JSON消息里带有ID字段。Hibernate想要自己控制ID值所以抛出了这个令人感到困惑的异常。看这就是过度简化所带来的恶果。除了这个还有其他成千上万个同样令人感到困惑的异常。在Spring栈中一个子系统套着另一个子系统它们坐等你犯错然后再抛出应用程序崩溃异常来惩罚你。
然后你会看到满屏的堆栈跟踪信息里面满是这样那样的抽象方法。面对这种级别的抽象显然需要更多的逻辑才能找到你想要的内容。如此多的堆栈跟踪信息不一定是不好的但它也是在提醒我们这在内存和性能方面的开销究竟有多大
而零代码的“findUserByFirstName”方法又是如何被执行的Spring框架必须解析方法名称猜测程序员的意图构造类似抽象语法树的东西生成一些SQL语句……那么完成这个过程需要多少开销
在反反复复经历这样的过程之后在花了大量时间学习你本不该学习的东西之后你可能会得出相同的结论隐藏复杂性并不会带来简单性它只会产生更多的复杂性。
另一面是Node.js
Spring和Java EE非常复杂而Node.js却是一股清流。首先是Ryan Dahl在核心Node.js平台上所应用的设计美学。他追求别样的东西花了数年时间磨练和改进了一系列核心的Node.js设计理想最终得到一个轻量级的单线程系统。它巧妙地利用了JavaScript匿名函数进行异步回调成为一个实现了异步机制的运行时库。
然后是JavaScript语言本身。JavaScript程序员似乎更喜欢无样板的代码这样他们的意图才能发挥作用。
我们可以通过实现监听器的例子来说明Java和JavaScript之间的差别。在Java中监听器需要实现抽象接口还需要指定很多啰七八嗦的细节。程序员的意图的这些繁琐的样板中渐渐淹没。
而在JavaScript中可以使用最简单的匿名函数——闭包。你不需要实现什么抽象接口只需要编写所需的代码没有多余的样板。
大多数编程语言都试图掩盖程序员的意图这让理解代码变得更加困难。
但在Node.js中有一点需要注意回调地狱。
没有完美的解决方案
在JavaScript中我们一直难以解决两个与异步相关的问题。一个是Node.js中被称为“回调地狱”的东西。我们很容易就掉入深层嵌套回调函数的陷阱每个嵌套都会使代码复杂化让错误和结果的处理变得更加困难。但JavaScript语言并没有为程序员提供正确表达异步执行的方式。
于是出现了一些第三方库它们承诺可以简化异步执行。这是另一个通过隐藏复杂性带来更多复杂性的例子。
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);
});这是个模仿Unix cat命令的例子。async库非常适合用于简化异步执行顺序但同时也引入了一堆模板代码从而模糊了程序员的意图。
这里实际上包含了一个循环只是没有使用循环语句和自然的循环结构。此外错误和结果的处理逻辑被放在了回调函数内。在Node.js采用ES 2015和ES 2016之前我们只能做到这些。
Node.js 10.x中等价的代码是这样的
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);
});这段代码使用async/await函数重写了之前的逻辑。虽然异步逻辑是一样的但这次使用了普通的循环结构。错误和结果的处理也显得很自然。这样的代码更容易阅读也更容易编码程序员的意图也更容易被理解。
唯一的瑕疵是process.stdout.write没有提供Promise接口因此用在异步函数中时需要丢Promise进行包装。
回调地狱问题并不是通过隐藏复杂性才得以解决的。相反是语言和范式的演变解决了这个问题。通过使用async函数我们的代码变得更加美观。
通过明确定义的类型和接口提升清晰度
当我还是Java的死忠时我坚信严格的类型检查对开发大型的应用程序来说是有百利而无一害的。那个时候微服务的概念还没有出现也没有Docker人们开发的都是单体应用。因为Java具有严格的类型检查所以Java编译器可以帮你避免很多错误——也就是说可以防止你编译错误的代码。
相比之下JavaScript的类型是松散。程序员不确定他们收到的对象是什么类型那么程序员怎么知道该怎么处理这个对象
但是Java的严格类型检查同样导致了大量样板代码。程序员经常需要进行类型转换或以其他方式确保一切都准确无误。程序员需要花很时间确保类型是准确的所以使用更多的样板代码希望通过及早捕获和修复错误来节省时间。
程序员不得不使用复杂的大型IDE仅仅使用简单的编辑器是不行的。IDE为Java程序员提供了一些下拉列表用于显示类的可用字段、描述方法的参数帮助他们构建新的类和进行重构。
然后你还得使用Maven……
在JavaScript中不需要声明变量的类型所以通常不需要进行类型转换。因此代码更易于阅读但可能会出现未编译错误。
这一点会让你更喜欢Java还是痛恨Java取决于你自己。十年前我认为Java的类型系统值得我们花费额外的时间因为这样可以获得更多的确定性。但在今天我认为代价太大了使用JavaScript会要简单得多。
使用易于测试的小模块来扫除bug
Node.js鼓励程序员将程序划分为小单元也就是模块。模块虽小却能从一定程度上解决刚刚提到的问题。
一个模块应该具备以下特点
自包含——将相关代码打包到一个单元中强壮的边界——模块内部的代码可以防止外部代码入侵显式导出——默认情况下代码和模块中的数据不会导出只将选定的函数和数据暴露给外部显式导入——声明它们依赖哪些模块可能是独立的——可以将模块公开发布到npm存储库或其他私有存储库方便在应用程序之间共享易于理解——更少的代码意味着更容易理解模块的用途易于测试——小模块可以轻松进行单元测试。
所有这些特点组合在一起让Node.js模块更容易测试并具有明确定义的范围。
人们对JavaScript的恐惧源自它缺乏严格的类型检查所以可能很容易导致错误。但在具有清晰边界的模块中受影响代码被限于模块内部。所以大多数问题被安全地隐藏在模块的边界内。
松散类型问题的另一个解决方案是进行更多的测试。
你必须将节省下来的一部分时间因为编写JavaScript代码更容易用在测试上。你的测试用例必须捕获编译器可能捕获的错误。
对于那些想要在JavaScript中使用静态检查类型的人可以考虑使用TypeScript。我没有使用TypeScript但听说它很不错。它与JavaScript兼容同时提供了有用的类型检查和其他特性。
但我们的重点是Node.js和JavaScript。
包管理
一想起Maven我就头大。据说一个人要么爱它要么鄙视它没有第三种选择。
问题是Java生态系统中并没有一个核心的包管理系统。Maven和Gradle其实也很不错但它们并不像Node.js的包管理系统那样有用、可用和强大。
在Node.js世界中有两个优秀的包管理系统首先是npm和npm存储库。
有了npm我们就相当于有了一个很好的模式用来描述包依赖性。依赖关系可以是严格的指定具体的版本或者使用通配符表示最新版本。Node.js社区已经向npm存储库发布了数十万个包。
不仅仅是Node.js工程师前端工程师也可以使用npm存储库。以前他们使用Bower现在Bower已被弃用他们现在可以在npm存储库中找到所有可用的前端JavaScript库。很多前端框架如Vue.js CLI和Webpack都是基于Node.js开发的。
Node.js的另一个包管理系统是yarn它也是从npm存储库中拉取包并使用与npm相同的配置文件。yarn的主要优点运行得更快。
性能
曾几何时Java和JavaScript都因为运行速度慢而横遭指责。
它们都需要通过编译器将源代码转换为由虚拟机执行的字节码。虚拟机通常会进一步将字节码编译为本地代码并使用各种优化技术。
Java和JavaScript都有很大的动机让代码运行得更快。在Java和Node.js中动机就是让服务器端代码运行得更快。而在浏览器端动机是获得更好的客户端应用程序性能。
甲骨文的JDK使用了HotSpot这是一个具有多种字节代码编译策略的超级虚拟机。HotSpot经过高度优化可以生成非常快的代码。
至于JavaScript我们不禁在想我们怎么能期望在浏览器中运行的JavaScript代码能够实现复杂的应用程序基于浏览器JavaScript实现办公文档处理套件似乎是件不可能实现的事情是骡子是马拉出来溜溜就知道了。这篇文章是我用谷歌文档写的它性能非常好。浏览器端JavaScript的性能每年都在飞涨。
Node.js直接受益于这一趋势因为它使用的是Chrome的V8引擎。
下面是Peter Marshall的演讲视频链接他是谷歌的一名工程师主要负责V8引擎的性能增强工作。他在视频中描述了为什么V8引擎使用Turbofan虚拟机替换了Crankshaft虚拟机。
V8引擎中的高性能JavaScripthttps://youtu.be/YqOhBezMx1o
在机器学习领域数据科学家通常使用R语言或Python因为他们十分依赖快速数值计算。但由于各种原因JavaScript在这方面表现很差。不过有人正在开发一种用于数值计算的标准JavaScript库。
JavaScript中的数值计算https://youtu.be/1ORaKEzlnys
另一个视频演示了如何通过TensorFlow.js在JavaScript中使用TensorFlow。它提供了一个类似于TensorFlow Python的API可以导入预训练模型。它运行在浏览器中可用于分析实时视频从中识别出经过训练的对象。
基于JavaScript的机器学习https://youtu.be/YB-kfeNIPCE
在另一个演讲视频中IBM的Chris Bailey讨论了Node.js的性能和可伸缩性问题特别是在Docker/Kubernetes部署方面。他从一组基准测试开始演示了Node.js在I/O吞吐量、应用程序启动时间和内存占用方面远远超过Spring Boot。此外得益于V8引擎的改进Node.js每次发布的新版在性能方面都有显著的提升。
Node.js的性能和高度可伸缩的微服务https://youtu.be/Fbhhc4jtGW4
在上面的这个视频中Bailey说我们不应该在Node.js中运行计算密集型的代码。因为Node.js采用了单线程模型长时间运行计算密集型任务会导致事件阻塞。
如果JavaScript的改进还无法满足你的应用程序的要求还有其他两种方法可以将本地代码直接集成到Node.js中。最直接的方法是使用Node.js本地代码模块。Node.js工具链中包含了node-gyp可用于处理与本地代码模块的链接。下面的视频演示了如何集成Rust库和Node.js
JavaScript与Rust集成远比你想象得简单https://youtu.be/Pfbw4YPrwf4
WebAssembly可以将其他语言编译为运行速度非常快的JavaScript子集。WebAssembly是一种可在JavaScript引擎内运行的可执行代码的可移植格式。下面的视频做了一个很好的概述并演示了如何使用WebAssembly在Node.js中运行代码。
在NodeJS中使用WebAssemblyhttps://youtu.be/hYrg3GNn1As
富Internet应用程序RIA
十年前软件行业一直热议利用快速的JavaScript引擎实现富Internet应用程序从而取代桌面应用程序。
这个故事实际上在二十多年前就已经开始了。Sun公司和Netscape公司达成了共识在Netscape Navigator中使用Java小程序Applet。JavaScript语言在某种程度上是作为Java小程序的脚本语言而开发出来的。服务器端有Java Servlet客户端有Java Applet这样就可以在两端使用同样的一门编程语言。然而由于各种原因这种美好的愿望并没有实现。
十年前JavaScript开始变得足够强大可以实现复杂的应用程序。因此RIA被认为是Java客户端应用程序的终结者。
今天我们开始看到RIA的想法得以实现。服务器端的Node.js和两端都有的JavaScript让这一切成为可能。
当然Java作为桌面应用程序平台的消亡并不是因为JavaScript RIA而是因为Sun公司忽视了客户端技术。Sun公司把注意力放在要求快速服务器端性能的企业客户身上。当时我还在Sun公司任职我亲眼看着这件事情发生。真正杀死Applet的是几年前在Java插件和Java Web Start中发现的一个安全漏洞。这个漏洞导致全球一致呼吁停止使用Java Applet和Java Web Start应用程序。
我们仍然可以开发其他类型的Java桌面应用程序NetBeans和Eclipse IDE之间的竞争仍然存在。但是Java在这个领域工作是停滞不前的除了开发工具之外很少有基于Java的应用程序。
JavaFX是个例外。
10年前JavaFX意欲成为Sun公司对iPhone的反击。它用于开发基于Java的手机GUI应用程序想把Flash和iOS应用程序打垮。然而这一切都没有发生。JavaFX现在仍然可以使用但没有了当初的喧嚣。
这个领域的所有兴奋点都发生在React、Vue.js和类似的框架上JavaScript和Node.js在很大程度上要得益于此。
结论
现在开发服务器端应用程序有很多选择。我们不再局限于“P”开头的语言Perl、PHP、Python和Java我们还有Node.js、Ruby、Haskell、Go、Rust等等。
至于为什么我会转向Node.js很明显我更喜欢在使用Node.js编程时的那种自由的感觉。Java成了负担而Node.js没有这样的负担。如果我再次拿起Java那肯定是因为有人付了钱。
每个应用程序都有其真实需求。只是因为个人喜欢而一直使用Node.js也不见得是对的。在选择一门语言或一个框架时总归是有技术方面的考量的。例如我最近完成的一些工作涉及XBRL文档由于最好的XBRL库是用Python实现的所以就有必要学习Python。 转载地址http://www.infoq.com/cn/articles/why-is-a-java-guy-so-excited-about-node-js-and-javascript 英文原文https://blog.sourcerer.io/why-is-a-java-guy-so-excited-about-node-js-and-javascript-7cfc423efb44