主力语言从C/C++(当然C/C++的差异,可能远比C和Go大)切到Go两年,最近又开始从事C++领域的开发工作,被各种layout和构建问题折腾到头疼。索性发发牢骚。
“大道至简”
从C++切到Go的过程还是比较清爽的,Go推崇“大道至简”,其语法层面实在没太多难点。但相对的,很多C++中能实现的特性,Go中缺乏相应语法特性,写起来不免难受。
比如枚举的缺失,大多数语言都有枚举类型,更别提静态类型语言,C++用enum class可以轻松实现强类型枚举。
1
enum class Color { Red, Green, Blue };
Go缺乏原生枚举类型,放弃自己静态类型检查的优势,除了创始人过于老派外,实在没有很好的理由。为了不引入枚举引入的iota更是奇怪至极,没有其他语言follow这个设计。
1
2
3
4
5
// iota,什么玩意?
const (
Red int = iota // 0
Green // 1
)
用type新建类型等方式也只能稍微缓解此问题,距离实际的强类型枚举还差的很远。
长期缺乏泛型支持更是备受诟病,尽管在开发者的呼声下,终于在1.18版本引入了泛型,到今为止其泛型能力仍然不强,标准库中也缺乏支持,刚使用Go时,看见sync.Map
容器中的interface{}
简直不敢相信自己眼睛,做不了编译时的类型检查,只能依赖封装加单元测试保证,更别说和原生map
语法的不统一,作为以“大道至简”自诩的语言,却在语言层面给map
这种类型以特殊地位,实在不该。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 内置 map 的简洁语法
m := make(map[string]int)
for k, v := range m { // 直接、简洁
// ...
}
var safeMap sync.Map
safeMap.Range(func(key, value interface{}) bool {
// 两个痛点:
// 1. 必须使用回调函数,而非直接的 for-range
// 2. key 和 value 都是 interface{},仍需类型断言
strKey, ok1 := key.(string)
intValue, ok2 := value.(int)
if !ok1 || !ok2 {
return false // 处理类型错误或终止遍历
}
// 使用 strKey 和 intValue...
return true // 继续遍历
})
“大道至简”下,语言入门极快,而且由于表达方式有限,无论是新手还是老手,写出的代码在局部风格上差异很小,可读性极高。尤其意外的是,如今AI辅助编程大行其道,这种语法简单、风格单一的特点大大提升了大模型生成代码的准确性和可用性,尤其是和C++这种因语法困难、风格多样而名声在外的语言相比,简直天差地别。
并发
Go的并发曾是它最被广为宣传的优势——一个go关键字就能起一个协程(goroutine),再配合channel做同步,新手也能写出并发安全的代码。
不过现在来看,各语言要在这点上追上Go并不困难,无甚至C++这种委员会推动的语言也在C++20版本在标准库版本引入了协程。与之相对的是网络库,作为历史悠久且被广泛使用的语言,C++网络库的标准库方案一再延时,无法敲定,实在令人发笑。
layout
C++语法风格碎片化人所共知,C with class、OOP、metaprograming、functional,可以说不同风格的C++从观感上几乎都是不同的语言,而大家比较容易忽略的是一个项目的物理设计(physical design),C++在这方面的碎片化也不遑多让。
比较直观的例子如project layout,Go社区Standard Go Project Layout已经成为了事实标准。而C++,稍微流行的layout方案是Pitchfork Layout,但远远谈不上作为事实标准,且里面对头文件(又一个糟糕设计,C++20总算引入了模块的概念,但距离全面使用还为时尚早)应该和源文件合并(merged)还是分离(separate)这种基本问题上也没有定论,更不用说include还分内部头文件和外部头文件的不同情况,布局方案难以抉择。
自带电池
从Go切回C++,最让我痛苦的不是语法,而是工具链。go build、go fmt、go vet、go test,开箱即用。至于C++,构建用Makefile、autoconf、CMake还是Bazel,格式化工具用clang-format可能还能达成共识,但风格采用Microsoft还是Google,甚至是逆天的GNU风格(不幸在VPP开发中使用过该风格)又会在团队内产生口水战,
C++还缺乏成熟的包管理,无论是vcpkg还是conan,都可以说流行度极低,很多项目宁愿自己“造轮子”,也不用第三方库。包管理曾经也是Go的痛点之一,但在Go 1.12后也随着Go Modular标准化而解决(无论这个方案是否让你满意)。
在我看来,告诉大家自带电池是多么的有用,可以说是Go语言对编程世界最大的贡献之一,现如今新诞生流行的语言,没有“开箱即用的标准化工具链”恐怕都很难再流行了。