谈谈Go语言宏观理解之一 - 鲜明特性| 青训营笔记

135 阅读5分钟

在 Go 语言官方文档中读到了 Go 语言设计理念的内容,讲的十分全面。计划用系列文章整理一下对Go语言的宏观理解。主要是翻译自原名为 Go History 的文章:Frequently Asked Questions (FAQ) - The Go Programming Language: go.dev/doc/faq

先列一下文章链接:

这篇文章主要梳理一下Go在语言层面的鲜明特性。

Go 运行环境

Go 语言确实有一个可拓展库,叫做 runtime,它是每个 go 语言程序的一部分。Go 语言的 runtime 库实现了垃圾回收、并发、栈内存管理,以及其他的关键语言特性。尽管 Go runtime 提供了很多关键的语言特性,但它与 C 语言中的 libc 库更加类似。

因此使用 Go 语言必须牢牢把握,Go 语言并不像 Java runtime 一样提供了虚拟机。所有 Go 语言程序必须提前编译为本地机器代码才能在目标机器上运行。所以,尽管“runtime”一词通常用来描述运行在虚拟环境中的语言,在 Go 语言体系中 runtime 仅代表为 Go 语言提供关键语言特性的库。

类型系统:Go 是否有泛型?

Go 语言在 1.18 版本发布时增加了类型参数。类型系统中加入类型参数将使得多态或泛型编程在 Go 语言中成为可能。查阅 Go语言标准Go 语言提案 了解详情。

Go 语言在发行之处并不带有泛型特性。因为 Go 语言发明之初仅仅是为了能够编写易于长期维护 的服务端代码,所以 Go 语言一开始的设计理念就是围绕扩展性、可读性和并发性能展开的。多态和泛型编程在当时并不是 Go 语言要达成的核心目标,因此 Go 语言设计之初就为了简洁起见省略了多态特性。

泛型特性虽然方便,但是给类型系统和运行时处理带来了额外的复杂性。Go 语言经过一段时间的发展才研发出一套价值与其复杂性成比例的设计方案。

异常与断言:存废之争

传统的编程语言一般有异常处理和断言机制。以Java为例,编程人员常常在程序中 try-catch-finally 这样的语句表达异常处理逻辑,而断言在生产环境中则是默认关闭的(JVM默认关闭断言,通过虚拟机参数 -ea选项开启断言后才能在程序中正常使用断言功能)。相比Java这样“臃肿的”语言,Go直接宣布不支持断言,甚至使用一种截然不同的方式来支持错误处理。较为新奇,也需要广大程序员经过一定程度的了解与适应。

没有异常处理

Go 语言认为遵守常规的 try-catch-finally 范式,把异常处理耦合进控制流程中,会使得代码变得令人费解、难以维护。这种常规的异常处理范式也在鼓励程序员把太多不必要的情况标记为异常,例如:文件打开失败。

Go 语言采用一种不同的方法来处理普通异常。对于简单的错误处理,Go 语言的多返回值机制让编程人员不必过载函数的返回值即可轻松报告异常。查阅 A canonical error type, coupled with Go's other features 了解详情,Go 语言的错误处理轻松愉快,不过也因此和其他语言有很大不同。

Go 语言也有一些内置函数可以用来标志真正的意外情况,并从中恢复。错误恢复机制仅在发生错误后函数处于被挂起状态时,作为函数状态的一部分执行。这样的错误恢复机制已经足够处理程序真正的异常,并且不需要额外的控制流。如果利用得当,通过简洁的代码即可实现错误恢复功能。

查阅 Defer, Panic, and Recover 一文了解详情。另外,Errors are values 这篇博客文章也描述了利用 Go 语言中异常也是值类型的特性,在 Go 语言中实现简洁的异常处理。

没有断言

Go 语言不提供断言机制。断言毫无疑问十分方便。但是我们从经验得出大部分程序员使用断言是为了避免考虑合理的错误处理和错误报告。合适的错误处理能让程序遇到非致命错误时继续运行,合适的错误报告意味着错误消息直击要害,避免程序员花费大量时间绞尽脑汁分析崩溃日志。精准的错误消息在程序员接触/运维不熟悉的代码时至关重要。

我们理解断言的存废是一个争议焦点。不过 Go 语言及其库中有很多要素并不符合现代工程实践,仅仅因为我们认为有时候不同的方法同样值得一试。

参考文章

  • Frequently Asked Questions (FAQ) - The Go Programming Language: go.dev/doc/faq