在决定重写 dnscrypt-proxy 这个项目之后,作者发现即使最初用的是 C 语言,有一些很好的库,但用其开发异步网络功能仍然需要写大量的代码,对应的开发者社区也正在消亡。因此,作者尝试使用 Go 语言进行重写,即便被很多人反对,但作者依旧决定这么做,并发现这是 Ruby 后第二个让作者感受到编程乐趣的语言。
与 Ruby 结缘
我是在Ruby on Rails发布的时候与 Ruby 结缘的。当时,PHP 在 Web 开发领域无处不在,但还没有真正意义上的框架。Rails 的出现改变了游戏规则,约定优于配置(Convention over Configuration)是一个很神奇的东西。按照约定起名字,框架知道如何让应用程序的其他部分来访问这些命名,不需要编写任何多余的代码!使用 Rails 开发网站的体验非常棒,它会帮助处理很多事情。这样,开发人员就可以专注在描述逻辑而不是实现细节上。
比起Rails,我更喜欢 Ruby。对于 Ruby 解释器和编译器开发人员来说,Ruby 的语法有点糟糕。但作为一名普通的开发人员,使用 Ruby 是一种乐趣,这是一种非常灵活的语言,做一件事情可以使用多种不同的方法。但如果被滥用,可能会成为一个弱点(运行时修改会导致 Ruby 变成一种完全不一样的语言),但这也是 Ruby 非常好的特性。
Ruby 不像Rust那样,编译器会不停地出问题,即使你在尽力遵守制定的规则。不管怎样,在使用 Ruby 时,开发人员可以用一种简单而自然的方式表达想要做的事情。人们经常说 Ruby 是为开发人员的幸福感而进行的优化,这绝对是真的,可以使用 Ruby 快速且毫不费劲地完成工作。Ruby 不只是一门利基语言,它的可用模块(gem)数量很惊人,想要为开发应用程序找到合适的工具通常都不是问题。
当然,Ruby 代码的运行速度不会像其他语言那样快。但在效率方面,Ruby 是首屈一指的。一个有实际用处的应用程序比运行速度快但不成熟的应用程序更有价值。
在 OpenDNS(现在是 Cisco)工作期间,我几乎什么事情都用 Ruby 来完成。我是数据处理团队的一员,我们可以自由地使用任何一门编程语言来完成任务,所以我选择了 Ruby。
Hadoop的作业也是用 Ruby 开发的(这要感谢 JRuby)。当然,如果这些东西是用 Java 开的,会运行得更快,但 Ruby 是一种可用于快速试错然后做出改进的语言,在这方面,Ruby 是完美的。
我用 Ruby 在周末开发的一个项目(DNS 数据库)现在成了思科的一款核心产品,而且卖得很火。当时,我只是想基于我们现有的数据做一些实验而已。如果我用了其他效率不那么高的语言,我还会完成这个项目吗?可能不会。因为那样的话一个周末可能不够,而且也不可能在我的业余时间完成这件事。
后来,我用 C 语言和 Rust 重写了部分东西,让运行速度更快一些,但如果没有之前 Ruby 的效率和易用性,这个项目可能永远不会存在。
那个时候,我的大多数开源项目都是用 C 语言或 PHP 开发的(还有其他一些我接触过的语言,比如 Erlang)。但我在笔记本电脑和服务器上运行的所有闭源脚本都是用 Ruby 编写的。我的个人网站也是用 Ruby 开发的。因为我只是想让这些脚本做我需要做的事情,不需要花很多时间在如何编写它们上。
试试 Go 语言
在 Rust 发布第一个公开版的时候,我试了一把,因为尝试新语言总是很有趣。虽然说所有的编程语言都很糟糕,但都带来了一些有趣的概念,这些概念可以让你成为更好的程序员。
从是否对开发人员友好的角度来看,Rust 与 Ruby 是完全相反的。早期的 Rust 与现在的 Rust 有很大的不同。在 OVH 的时候,我还学了Scala,因为需要开发 Spark 应用程序,我不得不用 Scala 来写代码。那个时候 Ruby 用得并不多,但仍然有很多运行在服务器端的东西是用 Ruby 开发的,它们不怎么需要维护。
所以,我使用 Ruby 的机会越来越少,但有越来越多的机会使用 Rust,尽管这门语言最终让我变成了一个脾气暴躁的人,完全不像使用 Ruby 时那样享受写代码的乐趣。
有一天,我想要完全重写 dnscrypt-proxy(https://github.com/DNSCrypt/dnscrypt-proxy)。
我用 C 语言开发了最初的版本,但维护这些代码非常痛苦。即使 C 语言有一些很好的库,但用它开发异步网络功能仍然需要写大量的代码,但获得的效果却很有限,而且还要很费劲地确保所有东西都能可靠、安全地运行。
有些功能已经计划了一段时间(比如匿名 DNS),但没有时间去实现它们。C 语言是一种奇妙的系统语言,但在其他方面并不是最有效率的语言。即使是增加一个很小的功能也会花费我更多的时间。
C 语言的另一个缺点是开发者社区正在消亡。自从 dnscrypt-proxy 这个项目开始以来,用于改进代码或添加新功能的拉取请求(PR)数量为零。即使这个项目有一个相当大的用户群,包括企业用户。
因此,我决定试试 Go 语言。
我没有使用 Go 语言的经验,我只知道它有“goroutine”的概念,这看起来很有趣。另外,它的编译速度也很快。这一决定受到了 dnscrypt-proxy 前用户的严厉批评:Go 太臃肿了!看这些二进制文件有多大!看看内存使用情况!而且可能运行得很慢!
现在回想起来,选择 Go 语言可能是我做过最好的决定。如果是在今天,我一定会再选它,让我来说一下我的理由。
Go语言比 Ruby 更有见地。与那些鼓励使用复杂结构来显摆的语言不同,Go 代码简单易读。Go 语言很稳定,而且向后兼容。因此,为了支持新系统,或者为了提高应用程序的速度,我只需要用新版本重新编译就可以了。
Go 编译器的运行速度非常快。在进行快速迭代时,这个非常有用,因为在修改少量代码后可以立即编译,然后进行测试。用来开发 Go 代码的工具非常好用。VSCode 自动添加导入(但这并不总是一个好主意,稍后会详细介绍),为表达式找到正确的类型,并且提供了自动完成功能(感觉非常棒,特别是对于从 Rust 转过来的人来说)。Go 语言的文档非常出色,提供了很多示例,而不仅仅是内部细节。
尽管我之前没有使用 Go 语言的经验,但我很快就得到了我想要的东西。那时,dnscrypt-proxy 的 C 语言版本已经有 5 年的历史,我在一周内重写了它的部分功能。实现一个基本的代理只需要 15 分钟。
除了 Go 语言和相应的工具,我完成这些东西还要多亏了 Go 语言的可用模块,以及它优秀的标准库。是的,还有 goroutine。
Go 语言是一门非常高效的语言。我喜欢用 Go 语言写代码,因为我可以看到应用程序按照我想要的方式演化,而不需要修改很多代码,也不需要在添加一行代码前花很多时间思考可不可以通过编译。
Go 语言的另一个优势是可移植性。当然,很多语言也是高度可移植的,不过 Go 的库在设计时都考虑到了可移植性,如果有必要的话可以在提供统一接口的情况下模拟所有缺失的东西。
我主要用 MacBook 写代码,但有了 Go 语言之后,我可以非常自信地说,相同的代码可以在任何受支持的平台上以完全相同的方式运行。
Go 语言的交叉编译比我见过的任何编译器都要简单。现在,dnscrypt-proxy 的每个版本都被自动打包成 23 个不同的语言版本,因为这样做非常简单,而且我确信它们在其他平台上运行与在我的开发平台上运行时的行为是完全一样的。
Go 语言有指针,生成的代码到处都有边界检查。与 C 语言不同,如果指针没有被正确使用,程序会自动安全崩溃,并打印出全面的堆栈跟踪信息。
说到堆栈跟踪和调试,Go 语言在这方面绝对是很棒的。Go 语言的堆栈跟踪信息简短、易读,并且包含需要的所有内容,用 VSCode 和 delve 来调试 Go 代码也很棒。
Go 语言的生态系统很大,无论何时需要什么,都能找到需要的模块。Go 语言的社区很强大,每当我被问题困住的时候,总能从社区中找到答案。
我在 Go 语言中找到了最初在 Ruby 中找到的东西。Go 语言是一种让我可以用自然的方式表达想法的语言。我的应用程序迭代得很快,编程再次让我感到兴奋。
“但 Go 语言是有 GC 的!”
是的,那又怎样?就像 Java 一样,如果我需要速度,可以预先分配和重用内存来避免 GC。我可以先用一种简单而自然的方式编写想要的代码,然后花时间对其进行优化,避免后面出现 GC 暂停。
因此,Go 语言的运行速度并不慢,可以 FastHTTP(https://github.com/valyala/fasthttp)为例。
“但它无法防止出现数据竞争!”
Rust 迫使我以一种不自然的方式写代码。我发现自己花了太多时间在思考代码是否可以通过编译,而有些问题在运行时可能并不会发生。有时候,我也会欣赏语言的严格性,它迫使我以某种方式设计代码,避免出现某些问题。但是,我也欣赏与我心智模型匹配且不会给我造成障碍的语言,特别是如果这门语言有很棒的调试工具。
Go 语言的效率让我可以更多地关注算法而不是实现。总的来说,Go 语言大获全胜。根据反馈,dnscrypt-proxy 的第二个版本比 C 语言版本要快得多。更重要的是,它提供了大量功能,如果我没有改用 Go 语言,或许就永远不会实现这些功能。
对我来说,Go 语言已经成了新的 Ruby。我用它来完成工作,并再次享受编程的乐趣。
原文链接:
https://00f.net/2019/10/28/go-is-the-new-ruby/
评论