内容简介:翻译自<A theory of modern Go> by Peter Bourgon 2017/06/09全文结论:
翻译自<A theory of modern Go> by Peter Bourgon 2017/06/09
全文结论:
全局状态会产生巨大的副作用 ——> 需要避免包级别的变量和init函数
Part1
Go is easy to read
Go语言唯一最佳的属性是基本上没有什么魔法代码。除了极少数的例外外,直接阅读 Go 的源码不会产生诸如“定义”,“依赖关系”,“运行时行为”的歧义,而这让Go的可读性较好,从而使得Go代码较容易维护,这是工业化编程的最高境界。
Part2
Magic is bad
但是魔法代码仍然有一些方式混入其中。不幸的是,非常普遍的一种方式是通过使用全局状态。包级别的全局对象可以对外部调用者隐藏状态和行为。调用这些全局变量的代码可能会产生意外的副作用,从而破坏了读者理解和脑海中构建程序的能力。
函数(包括方法,在go中二者略有不同)基本上是Go用来构建抽象的唯一机制。
思考以下函数定义:
func NewObject(n int) (*Object, error)
Part3 *
按照惯例来讲,我们希望形式为NewXxx的函数是类型构造函数。而这个函数也确实是构造函数,因为我们看到函数返回指向对象的指针和错误。由此我们可以推断出构造函数可能构造成功也可能构造失败,如果构造失败,将收到error告诉我们原因。
该构造函数参数为单int,我们假定该int参数控制了函数返回对象Object的生成。我们假定对参数int n有一些约束,如果不满足约束将导致错误。但是由于该函数不接受其他参数,因此我们希望它除了分配内存外应该没有其他副作用。
仅通过阅读函数签名,我们就可以得到这些推论,脑海中大概就有此函数了。从main函数的第一行开始重复递归的应用这个过程,是我们阅读和理解程序的方式。
假定这是NewObject函数的实现:
func NewObject(n int) (*Object, error) { row := dbconn.QueryRow("SELECT ... FROM ... WHERE ...") var id string if err := row.Scan(&id); err != nil { logger.Log("during row scan: %v", err) id = "default" } resource, err := pool.Request(n) if err != nil { return nil, err } return &Object{ id: id, res: resource, }, nil }
该函数调用了:
1.包级别的全局变量 database / sql.Conn,以对某些未指定的数据库进行查询;
2.包级别的全局记录器,用于将任意格式的字符串输出到某个位置;
3.以及包级别的某种类型的链接池对象,以请求某种类型的资源。
所有这些操作都有副作用,而这些副作用从函数签名则完全不可见。调用者没有办法预测这些事情发生,除非通过阅读函数体并跳到所有全局变量的定义处查看。
考虑另一种形式的签名函数:
func NewObject(db *sql.DB, pool *resource.Pool, n int, logger log.Logger) (*Object, error)
通过将每个全局依赖作为参数,我们使读者可以准确地知道函数的作用范围和在函数体内可能发生的行为。调用者确切地知道该函数需要什么参数,并可以提供这些参数。
如果我们正在为此程序设计公共API,我们甚至可以采取更有效的措施。
// RowQueryer models part of a database/sql.DB. type RowQueryer interface { QueryRow(string, ...interface{}) *sql.Row } // Requestor models the requesting side of a resource.Pool. type Requestor interface { Request(n int) (*resource.Value, error) } func NewObject(q RowQueryer, r Requestor, n int, logger log.Logger) (*Object, error) { // ... }
通过将每个具体对象抽象为接口,及仅捕获函数中使用到的方法,我们允许调用者自己去实现。这减少了包之间的源码级耦合,并使我们能够模拟测试中的具体依赖关系。如果对使用具体的包级别全局变量代码进行测试,我们会发现这种做法是多么乏味且容易出错。
如果我们所有的构造函数和其他函数都 显式地接受了它们的依赖关系 ,那么全局变量就没有任何用处。相反,我们可以在主函数中构造所有数据库连接,日志记录,链接池,以便 将来的读者 可以非常清楚地绘制出组件图并使用。
而且,我们可以非常明确地将这些依赖关系传递给使用它们的组件/函数,从而不会对全局变量感到困惑。另外值得注意的是,如果没有全局变量,那么也就不再需要使用init函数了,init函数的唯一目的是实例化或改变包级别的全局状态。
Part4
Try to write go without global state
编写几乎没有全局状态的Go程序不仅可能,而且非常容易。以我的经验来看,以这种方式编程不会比使用全局变量缩小函数定义慢或乏味。
相反,当函数签名可靠且完整地描述了函数主体的作用范围时,我们可以更高效地进行代码推理,重构和维护。 Go kit 从一开始就以这种风格编写,并因此受益。
Part5
Avoid two things
综上所述,我们可以发展出现代Go理论。 根据 Dave Cheney所述,提出以下准则:
- 避免包级别的变量
- 避免初始化函数
当然也存在例外。
欢迎关注我们的微信公众号,每天学习Go知识
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法设计与分析导论
R.C.T.Lee (李家同)、S.S.Tseng、R.C.Chang、Y.T.Tsai / 王卫东 / 机械工业 / 2008-1 / 49.00元
本书在介绍算法时,重点介绍用干设计算法的策略.非常与众不同。书中介绍了剪枝搜索、分摊分析、随机算法、在线算法以及多项式近似方案等相对较新的思想和众多基于分摊分析新开发的算法,每个算法都与实例一起加以介绍,而且每个例子都利用图进行详细解释。此外,本书还提供了超过400幅图来帮助初学者理解。本书适合作为高等院校算法设计与分析课程的高年级本科生和低年级研究生的教材,也可供相美科技人员和专业人七参考使用。一起来看看 《算法设计与分析导论》 这本书的介绍吧!