GTLC全球技术领导力峰会·上海站,首批讲师正式上线! 了解详情
写点什么

Go 语言编程模式

2016 年 4 月 07 日

在 2016 年伦敦举办的 QCon 大会上,Peter Bourgon 做了《六年Go 语言设计经验》的报告,重点探讨了在使用Go 进行开发时的编程模式和反模式。在这里,我们将他给Go 开发者的建议进行了简单的总结。

GOPATH:将 _GOPATH/bin_ 添加到“PATH”这个环境变量中,以便 Go 应用可以访问所需要的二进制文件。在绝大多数场景下,Bourgon 建议使用全局唯一的 _GOPATH_。有些开发者希望严格区分自己的代码和外部依赖代码,这些人更倾向于创建两个 _GOPATH_ 条目。开发者也可以选择不设置环境变量,并针对每个工程都使用 gb构建。

代码仓库的结构: 代码仓库的结构依赖于项目结构。如果是私人项目,开发者可以选择自己喜欢的任何结构。如果是开源项目,开发者最好遵循 Remote Packages 的建议,以便 _go get_ 命令引用该项目的包。Bourgon 建议创建一个基础目录,其中要包含程序的主要构件,以及放置帮助包的子目录,具体如下图所示:

代码格式化: Bourgon 强调开发者需要重视 Go 的权威的代码格式化风格,一旦开发者习惯这种风格,他的代码的可读性将大大提高。按照 Bougron 的观点,Go 开发者社区会认为非格式化的代码出自计算机新手。每次保存之前,可以使用 gofmt工具格式化代码。他认为 Go 代码审核指南为开发者和代码审核者提供了一套通用的实践规则。他还支持 Andrew Gerrand 关于 Go 开发的建议,包括如何为变量、函数和 exports 等元素命名,如果你能够遵循这些建议,阅读你代码的人将会非常感激你。

配置: Bourgon 建议配置管理应该有“清晰的定义和良好的文档支持。”他仍旧在使用来自标准库的 _flag_ 包,不过也希望这个包能够更简单易懂。他强调了明确定义配置项的重要性。通过环境变量传递配置项并没有为应用的使用者提供足够的信息去理解应用的参数使用,他建议在 _help_ 中提供必要的配置信息。

包名: 应该根据某个模块提供的服务而不是它的内容来定义包名。如果一个包含有 _HelloWorld_ 消息,那么它不应该被称为 _common_ 或 _consts_,而是 _greetings_。包名应该表明它所做什么,而不是它有什么。

点导入: Bourgon 建议不要使用“点导入”,这个特性通过设置点号来代替包名,使得开发者不需要明确的包名就可以访问相应包中的变量。这个特性降低了项目的可读性,尤其对于新手,新来的开发人员容易弄错哪个变量属于哪个包。Go——显式声明优于隐式声明。

Flags: Bourgon 并不认为在 _init()_ 方法而不在 _main()_ 方法中初始化 flags 是一个好主意,因为这使得这些 flags 无法在全局领域使用,而某些测试用例要用到这些 flags。

构造函数: 在谈到构造函数时,他建议将初始化的 _struct_ 以内联方式直接作为参数传入,从而避免传入无效或者未完成的状态,例如:

复制代码
foo := newFoo(*fooKey, fooConfig{
Bar: bar,
Baz: baz,
Period: 100 * time.Millisecond,
})

有意义的默认值: 不要使用 _nil_ 初始化某个变量,这使得每次在使用该变量的时候都需要进行空值检查,最好使用一个无操作值(no-operation value)进行变量初始化。例如,使用 _ioutil.Discard_ 初始化一个 _output_ 变量。

模块的交叉引用: 有些情况下会出现两个互相引用的模块。在构建其中的一个时,同时需要构建另一个模块,在构建后一个时又需要第一个先构建,下列两个 _structs_ 的定义就属于这种情况:

复制代码
type bar struct {
baz *baz
}
type baz struct {
bar *bar
}

Bourgon 提供了三种方法处理这种情况:

  • 整合:两个关系如此密切的对象应该整合成一个,在这种情况下应该整合成一个 _barbaz_ 结构体。
  • 分割:如果这两个模块必须保持分割,那么可以应用下列代码中采取的策略:
复制代码
type bar struct {
a *atom
monad
}
type baz struct {
atom
m *monad
}
a := &atom{...}
m := newMonad(...)
bar := newBar(a, m, ...)
baz := newBaz(a, m, ...)
  • 通信:当上述两种方法都不适用时,可以考虑在两个模块之间发送消息。
复制代码
type bar struct {
toBaz chan
<p><strong> 依赖:</strong> Bourgon 还提出了”明确依赖关系“的建议,例如:</p>
func (f *foo) process() {
log.Printf("bar: %v", result) // ...
}
<p> 应该写成下面这样:</p>
func (f *foo) process() {
f.Logger.Printf("bar: %v", result) // ...
}
<p><em>log.Printf</em> 实际上调用了 <em>Logger</em> 模块,这么写的话就隐去了这层依赖关系。为了明确这层依赖关系,开发者应该在构造过程中创建一个 <em>Logger</em> 对象,并使用 <em>ioutil.Discard</em> 代替空值 <em>nil</em></p>
<p><strong> 通道(Channel):</strong> Bourgon 建议,当多个协程(goroutine)之间共享内存时应使用 mutex,并通过通道对协程进行协调。</p>
<p><strong> 日志打印:</strong> 日志记录的代价很高,有可能成为应用的性能瓶颈。因此,建议只在绝对必要的地方记录日志,包括给开发者阅读或者供机器调用的信息。仅仅需要记录 <em>info</em><em>debug</em> 级别的日志。</p>
<p><strong> 监控工具:</strong> Bourgon 认为 Go 应用的监控代价很小,推荐开发者使用 <a href="http://prometheus.io/">Prometheus</a> 监控自己应用使用的各种资源。</p>
<p><strong> 全局状态:</strong> 消除隐式的全局依赖和全局状态。</p>
<p><strong> 测试:</strong> 执行包级别的测试。为了测试而设计:使用函数式编程风格——使用参数表明依赖关系、使用接口以及避免依赖全局状态。</p>
<p><strong> 依赖管理:</strong> 将所有依赖项都拷贝到项目的仓库中用于构建二进制代码。Bourgon 建议开发者根据自己的需要从 <a href="https://github.com/FiloSottile/gvt"><em>gvt</em></a><a href="https://github.com/dpw/vendetta"><em>vendetta</em></a><a href="http://github.com/Masterminds/glide"><em>glide</em></a><a href="https://github.com/constabulary/gb"><em>gb</em></a> 这几个工具中选择。</p>
<p><strong> 构建:</strong> 不要使用 <em>go build</em>,要使用 <em>go install</em>,因为后者可以缓存依赖关系,并把这些依赖关系放在 <em>GOPATH/bin</em> 下以便于调用。</p>
<p> 这些建议已经被应用于开发 <a href="https://github.com/go-kit/kit"><em>Go Kit</em></a>,一款用于构建微服务的分布式编程工具。</p>
<p>2009 年以来,Bourgon 在 SoundCloud 和 Weaveworks 两家公司都使用 Go 语言开发,开发了几款产品,包括:<a href="https://github.com/soundcloud/roshi">Roshi</a>——一款基于时间序列的事件数据库, 以及 Go Kit。</p>
<p>2016 年 QCon 大会上的 <a href="https://qconlondon.com/presentation/successful-go-program-design-6-years">《六年 Go 语言设计经验》</a> 视频将会在今年晚些时候对外公开。</p>
<p><strong> 查看英文原文:<a href="http://www.infoq.com/news/2016/03/go-patterns">Programming Patterns in Go</a></strong></p>
<hr></hr><p> 感谢 <a href="http://www.infoq.com/cn/author/%E9%82%B5%E6%80%9D%E5%8D%8E"> 邵思华 </a> 对本文的审校。</p>
<p> 给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 <a href="mailto:editors@cn.infoq.com">editors@cn.infoq.com</a>。也欢迎大家通过新浪微博(<a href="http://www.weibo.com/infoqchina">@InfoQ</a><a href="http://weibo.com/u/1451714913">@丁晓昀 </a>),微信(微信号:<a href="http://weixin.sogou.com/gzh?openid=oIWsFt0HnZ93MfLi3pW2ggVJFRxY">InfoQChina</a>)关注我们。</p>
2016 年 4 月 07 日 19:004596

评论

发布
暂无评论
发现更多内容

跟随美团技术大牛的脚步,感受虚拟机源码及调优所带来的独特魅力

周老师

Java 编程 程序员 架构 面试

Pod安装神策SDK报错Remote branch v2.1.3 not found in upstream origin

凌宇之蓝

ios 小程序flutter, 跨平台 CocoaPods pod React Native

mPaas 厂商push不通排查指南

阿里云金融线TAM SRE专家服务团队

android push

联盟:互联网时代的人才变革

非著名程序员

互联网 个人成长 人才 人才发展 突破圈层,个体崛起

你真的了解 Base64 吗

hepingfly

Java base64 编码

Elasticsearch初步认识

枫林

Java elasticsearch ES

Java集合源码学习笔记,Java程序员面试必备基础知识

Java成神之路

Java 程序员 面试 集合 架构师

Docker 安装和简单使用

枫林

Docker

在Alibaba广受喜爱的“Java突击宝典”简直太牛了

Java成神之路

Java 阿里巴巴 程序员 面试 架构师

关于MySQL参数,这些你要知道

Simon

MySQL 参数

还不懂JVM的设计原理与实现?赶紧跟着字节大牛“身临其境”

周老师

Java 编程 程序员 架构 面试

终端传感了解吗?18个知识点为你扫盲

华为云开发者社区

IoT 信息化 传感器 传输协议 无线传输器

指针变量的传值和传址

C语言与CPP编程

c++ 指针 C语言

C语言与C++常见面试题

C语言与CPP编程

c++ 面试题 C语言

Flink检查点存在的性能影响-16

小知识点

scala 大数据 flink

深挖 Redis 6.0 源码—— SDS

yanglbme

redis 源码 源码分析

架构师训练营0期 第十二周作业

WW

微服务下数据一致性的几种实现方式

xcbeyond

微服务 BASE理论 数据一致性

oeasy教您玩转linux010203显示logo

o

欢迎观看 AzureShow

亮小猪

云计算 开源 技术社区 azure 视频

数字货币交易所系统开发|交易所搭建源码

WX13823153201

我们一起学程序-五子棋

叫练

Java 多线程 游戏 websocket

甲方日常 7

句子

工作 随笔杂谈 日常

AtmoicXXX与AtmoicXXXArray源码分析

Darren

源码 内存布局 CAS java 并发 AtmoicXXX

浮点数比较的精度问题

C语言与CPP编程

c c++

Docker -快速安装Elasticsearch

枫林

最通俗易懂的 Redis 架构模式详解

哈喽沃德先生

redis 架构模式 redis集群 redis哨兵 redis主从

区块链usdt支付平台搭建|OTC承兑跑分系统开发

WX13823153201

区块链usdt支付平台搭建

承兑商USDT支付系统平台开发|跑分系统搭建

WX13823153201

面试官问:Spring Boot中Tomcat是怎么启动的

Java小咖秀

tomcat 面试 springboot

闲聊胡扯

C语言与CPP编程

随笔杂谈

DNSPod与开源应用专场

DNSPod与开源应用专场

Go语言编程模式-InfoQ