Russ Cox的2017年Go开发计划

注: Russ Cox目前是Go Team的leader

我的目标是帮助开发者。我想确保我们Go团队所做的工作对Go开发人员有重大积极的影响。这听起来很容易,但是有很多问题,例如:花费太多时间清理或优化不需要的代码;只回应最常见或最近的投诉或要求;或过分侧重于短期改进。重要的是确保我们的开发工作集中在最有效的地方。

这篇文章概述了我今年的几个重点。这只是个人的看法,而不是Go团队的。

发布本文的一个原因是想收集反馈。如果你有任何想法或建议,请随时评论或新建GitHub issue。

另一个原因是让大家明白我们知道这些问题很重要。人们常常认为Go团队缺乏行动是因为我们认为一切都是完美的,而实际上是我们有其他更高优先级的工作要做。

Type aliases 类型别名

在大型代码库重构期间将类型从一个包移动到另一个包时存在重复的问题。 我们试图用通用别名来解决它,这至少引入两个问题:我们没有解释该变化带来的问题,我们没有及时交付,所以并没有在1.8发布该功能。 前事不忘,后事之师,我做了一个演讲并写了一篇关于type aliases基本问题的文章,并开始在Go issue tracker 上发起有关解决方案的讨论。 现在看起来更有限的type aliases是正确的选择。 我想确保type aliases可以顺利随Go 1.9发布。

Package management 包管理

我设计了 goinstall(它成为“go get”)工具。自那以后发生了很多变化。 特别是其他语言生态系统真正提升了人们对包管理的期望,开源世界大多数人同意语义化版本,这为推断版本兼容性提供了基础。 Go需要在这里做得更好,有一组代码贡献者一直在努力解决这一问题。 我想确保这些想法都很好地集成到标准的Go工具链中,并使包管理成为人们喜欢Go的理由。

Build Improvement

go构建系统的设计存在一些早该被修复的缺点。这里有三个代表性的例子,我打算重新设计go命令来解决这些问题。

build速度太慢,因为go命令不积极地缓存build结果。许多人没有意识到用go install可以保存build结果,而go build不是。因此他们运行重复的go build命令会减慢构建过程。当修改依赖的时候,同样的情况也发生在go test(应使用go test -i)。

测试结果也应该被缓存:如果测试的输入没有改变,那么通常不需要重新运行测试。这将使得在很少或没有变化时运行“all tests”的开销降低。

在GOPATH以外应该与在GOPATH内的工作一样。特别是可以git clone一个repo,运行go命令,并使它们工作正常。软件包管理只是使这一点变得更重要:您需要能够在不同版本的包(比如v1和v2)上工作,而不需要为它们分配完全独立的GOPATH。

代码语料库

从前面type aliases部分提到的文章和演讲可以看到很多语料库的实际使用。 我们还定义vet的插件必须针对真实程序中经常发生的问题。 我想看到对实际实践的分析成为我们讨论和评估Go变化的标准方式。

现在没有一个商定的代码语料库用于这些分析(每个人都必须首先创建自己的),对每个人来说这都是一个太繁重的工作。 我想开放一个单独的,自包含的Git repo,其中包括我们的用于分析的官方基准语料库。 一个可能的起点可能是GitHub上的前100个Go语言库。

Automatic vet

Go带有go vet这个强大的工具。 当vet有输出的时候,你应该检查vet输出。 每个人都必须运行它。 特别是,我认为我们可以在go测试期间运行vet,而不必减慢编译 - 编辑 - 测试周期。 如果我们能做到这一点,并且我们启用的vet检查限制为100%准确,我们可以让pass vet成为运行测试的前提条件。 开发人员不需要记得去运行go vet。 他们运行go test。

Errors & best practices

Go error的一部分是调用的函数以及相关的可用context,包括正在尝试的操作(例如函数名称及其参数)。 例如,这个程序:

err := os.Remove("/tmp/nonexist")
fmt.Println(err)

输出如下:

remove /tmp/nonexist: no such file or directory

然而并非所有的go代码都像os.Remove这样加入context,大部分代码都是这样:

if err != nil {    
    return err
}

沿着调用堆栈,抛弃有用的context(像remove /tmp/nonexist 等)。 我想尝试理解我们对包含context的期望是否错误,或者我们可以做一些事情来帮助编写返回更友好错误的代码。

在社区中还存在关于剥离错误context接口的各种讨论。 我想尝试理解什么时候有意义,以及是否应该通过该建议。

Context & best practices

我们在Go 1.7中添加了新的context包,用于保存请求相关的信息,如超时,取消状态和认证信息。一个独立的context是不可变的(像一个单独的字符串或int):它只能导出一个新的,更新后的context,并且将该context传递到调用栈或者(较不常见的)传回调用者。context现在诸如database/sql和net/http之类的API中使用,主要是为了当调用者不再对结果感兴趣时可以停止处理请求。超时信息适合在context中保存,但是数据库选项不是,因为不太可能在请求期间执行的所有可能的数据库操作。当前时钟源或日志接收器怎么办?是否适合存储在上下文中?我想尝试理解和描述适合使用context的场景。

内存模型

go内存模型相比其他语言需要更多的编译器参与工作。 我认为核心编译器和运行时开发人员都同意这些原子行为应该大致与C ++ seqcst atomics或Java volitiles相同,我们仍然需要仔细地在内存模型中写下来,也可能是写一篇长博客。

Immutability

race detector是Go最受欢迎的功能之一。 但没有race会更好。 如果有一些合理的方法来将不变性集成到Go,这样程序员可以做出明确的检查断言什么可以和不能写,从而消除编译时的某些竞争。 Go已经有一个不可变类型-string(string是不可变[]byte的类型)。 虽然我不认为今年会引入这一变化,但我想更好地了解该解决方案。 Javari,Midori,Pony和Rust都在解决方案讨论中提出了有趣的观点,除此之外还有大量的研究论文。

从长远来看,如果我们可以静态地消除竞争,那将消除对大部分内存模型的需要。 这可能是一个不可能实现的梦想,但我想更好地了解该解决方案。

泛型

没有什么比Go是否应该支持泛型(或者多少年前应该发生)这一问题 更加能引起Go和非Go开发者激烈的争论。 我不相信Go团队曾经说过“Go不需要泛型”。我们所说的是Go面临的更高优先级的问题。 例如,更好的软件包管理支持将对大多数Go开发人员产生比增加泛型更大的直接积极影响。 但我们确实明白,对于Go来说缺乏参数多态性是一个显着的障碍。

就个人而言,我想能够编写一般的channel处理函数,如:

// Join makes all messages received on the input channels
// available for receiving from the returned channel.
func Join(inputs ...<-chan T) <-chan T
// Dup duplicates messages received on c to both c1 and c2.
func Dup(c <-chan T) (c1, c2 <-chan T)

我还希望Go支持高级数据处理抽象(类似于FlumeJava或C#的LINQ),在编译时捕获类型错误,而不是在运行时。泛型还会带增加来通用数据结构或算法实现的可能,但我个人觉得广泛的应用程序更引人注目。

为了找到正确的方式为go添加泛型,我们已经努力了多年。一些人建议暂停设计通用参数多态性(如chan T)以及string和[]byte。如果后者通过不变性的参数化来处理(如在前面部分中所描述的)则可能简化对于泛型的设计的需求。

当我在2008年开始考虑Go的泛型时,学习的主要例子是C#,Java,Haskell和ML。没有一种方法是完美的。现在,还有更新的尝试,包括Dart,Midori,Rust和Swift。

这是几年来,我们探索的设计空间。考虑到对不可变性的理解和其他语言的实现,这可能是再次审视泛型的时候了。我不认为泛型将在今年引入,但我想说我可以更好地理解相关的解决方案。

jack.zh 02.08 Go 阅读  900  次

Fork me on GitHub