放弃了用 Go 重写网站的决定

版权声明:所有博客文章除特殊声明外均为原创,允许转载,但要求注明出处。

我曾经说过,现在也仍然认为————个人网站就是用来折腾的。本站也经过几次重写,主要是想要体验不同的语言和框架在完成一个实际项目的时候有哪些差别。最近一次重写遇到了 Python 在性能方面的某些问题(具体看这里),因此我也开始考虑是否可以用性能比较强的静态语言来重写,以目前各种语言的发展态势来看,Go 似乎是一个比较合理的选择。

在此之前,我也曾简单接触过 Go,但尚未有机会用它去开发实际项目。当时的感觉是其语法和标准库的设计用起来都有点别扭,错误处理很容易写到手疼,对于用大小写表示可见性的做法也不太喜欢。另一方面,编译速度、一致的工具链和简单的部署也确实令人印象深刻。这一次,我决定重新把它捡起来,看看这两年里它有哪些变化;同时,希望了解它在数据库、Web 框架、爬虫、文本处理等方面,有哪些库或框架可用,以评估是否应该用它来重写我自己的网站。

通过一个多星期的学习和试用,我得出的结论如下:

  • 在语言方面,Go 给人的感觉没有太大变化————写起来还是很繁琐,不论错误处理、泛型或反射,要么没有,要么太弱,结果就是缺乏灵活的代码组织手段。Go 的拥趸们说这样写起来心智负担轻,照我看是把程序员变成人肉代码生成器,对于读代码的人来说也是额外的负担。
  • 由于 Go 内置了对 HTTP 的支持,实现一个 Web 框架并不算困难,因此可供选择的方案非常多。其中我个人比较喜欢的是 Echo,主要是看中它的路由非常简单且灵活,其他方面也没什么明显的短板。之所以看重路由也是基于对 Python Web 框架的使用经验:Flask 的 Blueprint 不能嵌套,对于复杂的路由还是有点捉襟见肘;Django 的路由通过命名空间可以设计得足够复杂,然而写起来繁琐且不直观,也容易出现难以排查的问题。
  • Go 的模板不好用,接口设计得也有些让人费解。好在前后端分离以后,模板基本上没什么用处了,这一条 Pass。
  • 数据库方面,用不用 ORM 似乎差别不大,由于 Go 的反射比较挫,还是免不了写很多样板代码。好处是没有那么复杂的抽象层次,因此发现和解决问题比较容易。
  • 在为人诟病比较多的包管理上面,确实是比较原始,可能由于我使用还比较少,也还没有发现过什么严重的问题。不过最近墙一直在加高,这方面多少还是有点影响效率...好吧,我知道这不怪它。

从感情上讲,我并不喜欢 Go 的风格。不过我对于语言的选择没有太大洁癖,如果确实能够显著提高网站性能、简化部署的话,那么代码写得挫一点也还是可以接受的。昨天,当技术问题基本都已经测试完毕以后,我几乎已经做出了把网站用 Go 重写的决定,不过最后阶段发现了两个小问题,最终让我放弃了这个决定。

这两个问题是:

  1. 当你设计数据库的时候,难免有些字段是可以为 null 的,而 Go 的基本类型多数不允许为空,这些字段在读取的时候就会出现问题。Go 解决此问题的办法是,用 sql.NullXXXX 代替常规字段(它们有一个 Valid 字段用来表示值是否为空)。但对于习惯了 POXO(Plain Old XXX Object)观念的我来说,模型不再纯粹、侵入了数据库特有的概念,是难以接受的;同时在序列化到 JSON 的时候也需要许多额外的工作。是的,我知道这个问题可以解决,但我希望看到的是在语言层面的支持,而不是把这个问题丢给程序员去处理。Go 官方给出的建议“尽量避免使用可空字段”,在我看来是一种逃避问题的态度。
  2. 我意外地发现,如果你想要格式化日期比如 time.Format(),那么格式化字符串不能是常见的 YYMMdd,也不是 %Y-%d 这样,而是必须使用 Go 诞生的准确时间戳,否则就会得到莫名其妙的结果。在发现这一点的时候,我真的是惊呆了。我承认设计 Go 的那帮人是大佬没错,但是实在难以想象要自恋到什么地步才能作出这样的设计。当然我也知道, Go 的 time 模块定义了一些常量,大多数情况下并不需要去手工输入这个时间,但我发自内心地无法接受这个设计。

好了,以上只是我个人在试用后的一些碎碎念。Go 仍然有一些优点是值得欣赏和学习的,但我不会用它来开发下一个项目,就是这样。