阅读 423

客观对比Node 与 Golang

Go仅用标准库就能写大多数的软件。用Node.js时,我们几乎都是不得不引入一个外部的库, 这样做既增加了部署的时间,也增加了来自第三方软件的潜在隐患。只用标准库能让我们写的代码更快更安全。

另外值得一提的是,node的代码在跨平台运行时,有时会碰到各种意想不到的问题,而go语言的代码实现跨平台运行时仅仅配置一下gopath即可完美兼容。

d

包管理

Go对包管理一定有自己的理解。对于包的获取,就是用go get命令从远程代码库(GitHub, Bitbucket, Google Code, Launchpad)拉取。并且它支持根据import package分析来递归拉取。这样做的好处是,直接跳过了包管理中央库的的约束,让代码的拉取直接基于版本控制库,大家的协作管理都是基于这个版本依赖库来互动。细体会下,发现这种设计的好处是去掉冗余,直接复用最基本的代码基础设施。Go这么干很大程度上减轻了开发者对包管理的复杂概念的理解负担,设计的很巧妙。

但是这样也会产生一系列的问题:

  • 缺乏明确显示的版本。团队开发不同的项目容易导入不一样的版本,每次都是get最新的代码。尤其像我司对开源软件管理非常严格,开源申请几乎是无法实施。

  • 第三方包没有内容安全审计,获取最新的代码很容易引入代码新的Bug,后续运行时出了Bug需要解决,也无法版本跟踪管理。

  • 依赖的完整性无法校验,基于域名的package名称,域名变化或子路径变化,都会导致无法正常下载依赖。我们在使用过程,发现还是有不少间接依赖包的名称已失效了(不存在,或又fork成新的项目,旧的已不存维护更新)。

而Go官方对于此类问题的建议是把外部依赖的代码复制到你的源码库中管理。把第三方代码引入自己的代码库仍然是一种折中的办法。

于是市面上就诞生了各种Go包管理工具,如glide,dev等。使用第三方包管理最大的好处是,每个项目都采用各自独立的包,而且可以很好的控制包的版本,这在团队开发中尤其重要。

而node的包管理我认为就相对好一些了,不管是npm还是yarn的包版本控制,都是棒棒哒。

其中还有一点需要注意的地方,就是glide的依赖是扁平化的,Glide 获取的所有依赖都放置在项目顶级目录下的vendor/中,而node中的依赖是集中存放在node_modules目录下,链式依赖过多,并且是递归查找。

类型系统

node是使用JavaScript这门动态语言、弱类型语言,好处自然不言而喻,比较灵活,忽略验证数据的类型和真值判断陷阱所带来的额外负担。但是JavaScript是在运行时进行解释的,这可能会导致错误处理和调试的问题。

go属于静态语言,也是强类型语言,虽然没有动态语言灵活,但有助于数据完整,并可以在编译时查找类型错误。Go 被直接编译成机器码,这就是它速度的来源。使用编译语言调试是相当容易的,因为你可以在早期捕获大量错误。

并发处理

node

node只适合IO密集型,它没有提供太多的并发基元。唯一能同时运行的是I/O程序和定时器等,并不适合CPU密集型。

node运行机制:node运行机制是事件循环机制,每次从事件队列中取出一个函数之类的,然后去运行它,如果过程中发生IO事件,比如利用fs模块写一个文件,或者去数据库查询信息等,node就会将这个IO操作加入到一个线程池中去执行,事件循环在主线程继续执行,当线程池中的事件执行完毕,就会将这个结果放入到主线程中。但是如果遇到计算密集型的任务,因为node是单线程,就会阻塞主线程直到该任务执行完毕才会往下执行,所以node不适合做CPU密集型。

d

go

go适合IO密集型同样也适合CPU密集型,你可以在程序运行的任何阶段,创建goruntine去实现并发,并且go提供了channel来实现协程间通信,很赞有木有。

在操作系统提供的内核线程之上,Go搭建了一个特有的两级线程模型。goroutine是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。关于go的并发及调度原理,戳这里goroutine 调度原理

d

错误处理

Go 推荐在错误出现的地方捕获它们,而不是像 Node 一样在回调中让错误冒泡。

node的错误处理是错误前置,golang的错误处理是错误后置。

// Node 的错误处理
foo('bar', function(err, data) {
// 处理错误
}
复制代码
//Go 的错误处理
foo, err := bar()
if err != nil {
// 用 defer、 panic、 recover 或 log.fatal 等等处理错误.
}
复制代码

Go 中的错误处理分为错误和异常两种,那么错误和异常的区别是什么呢?

错误和异常从语言机制上面讲,就是error和panic的区别

  • 错误是指可能出现的地方出了问题,比如打开一个文件失败,这种是在人们的意料之中;

  • 而异常指的是不应该出现问题的地方出现了问题,比如引用了空指针、下标越界、除数为0等,这种情况在人们的意料之外。

Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。

使用 Go 的错误处理时,应注意以下几点

  • 失败的原因只有一个时,不使用error

  • 没有失败时,不返回error

  • error应放在返回值类型列表的最后

  • 错误值统一定义,而不是跟着感觉走

  • 错误逐层往上抛时,层层都加日志,便于定位错误

  • 当尝试几次可以避免失败时,不要立即返回错误

  • 当上层函数不关心错误时,建议不返回error

Node中的错误处理主要分为以下三种情况

  • 异步的函数里,使用throw。使用者使用try...catch即可捕获错误。

  • 异步函数里,更常用的方式是使用callback(err, result)的方式。

  • 在更复杂的场景里,可以返回一个EventEmitter对象,代替使用callback。使用者可以监听emitter对象的 error事件。 例如读取一个数据流,我们可能会同时使用 req.on('data')、req.on('error')、req.on('timeout') 。

所以,使用throw还是callbacks、EventEmitter,取决于:

  • 该错误是操作错误还是编码错误?

  • 该函数是同步还是异步?

测试

在node中进行单元测试需要借助第三方测试框架入mocha以及第三方断言库入should.js,相关文章 Node.js 单元测试:我要写测试

使用Go的时候,我们喜欢测试框架的规范化。在Go里,所有的测试包都是内置的。如果你需要写一个新的测试套件,你必须做就是把(文件名)_test.go文件加到你要测试的软件的同一个包里,它将会在你每次执行go test的时候运行。

提到单元测试,就不得不提的是测试的覆盖率问题,在go中的测试覆盖率有一点需要注意的地方,在go中计算单元测试覆盖率是不包括没有定义_test.go的模块的,只会统计已经定义了_test.go模块的单元测试覆盖率。

部署

go的部署真的是非常简单,将go build后生成的二进制文件直接丢到服务器上去,然后运行这个程序,不需要任何语言环境,像java程序需要在服务器安装java,php需要安装Apache,PHP等运行环境,go统统不需要,只需要一个linux系统就好,扔上去就可以了。

而node的部署的部署则需要在服务器上安装npm,或者借助pm2,将项目代码拉倒服务器,并跑起来。

社区

Node 和 Go 岁数相仿,社区也相对都比较完整了

作者的 Golang 系列博文:

剖析 golang 的25个关键字

Array、Slice、Map原理浅析

Go 与 Node 内存分配与垃圾回收

goroutine 调度原理

关注下面的标签,发现更多相似文章
评论
说说你的看法