Golang 编码技巧分享(上)

0. 引子


阅读了Dave Cheney 关于go编码的博客:Practical Go: Real world advice for writing maintainable Go programs


实际应用下来,对我这个go入门者,提升效果显著。


我对作者的文章进行整理翻译,提取精炼,加上自己的理解,分享出来。希望也能给大家带来帮助。


希望大家支持原作者,原汁原味的内容可以点击 链接 阅读。文中部分例子为个人添加,如有不足敬请包容指出^ _ ^


(PS:如涉及侵权,请与我联系,我会及时删除文章,知识传播无界,望大家支持)


1. 指导原则


个人认为,编码的最佳实践本质是为了提高代码的迭代产能,减少bug的几率。(成本、效率、稳定)


作者Dave Cheney提到,go语言的最佳实践的指导原则,需要考虑3点


  1. 简洁

  2. 可读性

  3. 开发效率


1.1 简洁


简洁是对于人而言的,如果代码很复杂,甚至违法人的惯性理解,那么修改和维护是牵一发而动全身的。


1.2 可读性


因为代码被阅读的次数远远多于被修改的次数。在作者看来,代码被人的阅读和修改的需求,比被机器执行的需求更强烈。go编码最佳实践第一步就应该确定代码的可读性。

在我个人看来,类似于一致性算法中, raft为什么比paxos传播和应用更广,一个很重要的原因就是raft更加易于理解,raft作者在论文中也提到,raft设计的最重要的初衷就是,paxos太难懂了。可读性的重要性应该排在首位的。


1.3 开发效率


良好的编码习惯,可以提高代码的交流效率。使得同事们看到代码就知道实现了什么,而不必去逐行阅读,大大节约了时间,提高开发效率。

此外,对于go语言本身而言,无论在编译速度还是debug时间花费上,go相对C++也是开发效率大大提高的。


2. 命名

命名对编写可读性好的go程序至关重要!


曾经听到这样的一个言论:对变量的命名要像给自己孩子起名一样慎重。


其实,不光是变量命名,还包括function、method、type、package等,命名都很重要。


2.1  选择辨识度高的名字,而不是选择简短的名字


就像编码不是为了在尽量短的行数内,写完程序。而是为了写出可读性高的程序。


同样的,我们的命名标识也不是越短越好,而是容易被他人理解。


一个好名字应该具备的特点:


  1. 简短:一个好名字应该在具备高辨识度的情况下,尽量简短。


    1. 比如一个判断用户登录权限的方法:坏名字是judgeAuth(容易歧义),judgeUserLoginAuthority(冗长)

    2. 好的例子judgeLoginAuth

  2. 描述性的:一个好的名字应该是描述变量和常量的用途,而非他们的内容;描述function的结果,或者method的行为,而不是他们的操作;描述package的目的,而非包含的内容。描述的准确性衡量了名字的好坏。


    1. 比如设计一个用来主从选举的包。坏的package名字leader_operation,好的名字election

    2. 坏的function或者method名字ReturnElection,好的名字NewElection

    3. 坏的变量或者常量名字ElectionState,好的名字Role

  3. 可预测的:一个好的名字,仅通过名字,大家就可以推断他们的用途。应该遵循大家的惯用理解。下面会详细阐述。比如

    1. i,j,k常用来在迭代中描述引用计数值

    2. n通常用来表示计数累加值

    3. v通常表示一个编码函数的值

    4. k通常用在map中的key

    5. s通常用来表示字符串


2.2 命名的长度


关于名字的长度,我们有这些建议:


  1. 如果变量的声明和它被最后一次使用的距离很短,可以使用短的变量名

  2. 如果一个变量很重要,那么可以避免歧义,允许变量名称长一些,消除歧义

  3. 变量的名字中请不要包含变量的类型名

  4. 常量的名字应该描述他们保存的值,而不是如何使用该值

  5. 单个字母的名字可以用作迭代、逻辑分支判断、参数和返回值。包和函数的名字请使用多个字母的组合。

  6. method、interface、package 请使用单个单词

  7. pakcage名字也是调用方引用时需要注明的,所以请利用package的名字

举一个作者文中的例子说明:


Golang 编码技巧分享(上)_第1张图片


在这个例子中,people 距离最后一次使用间隔7行,而变量p是用来迭代perple的,p距离最后一次使用间隔1行。所以p可以使用1个字母命名,而people则使用单词来命名。


其实这里是防止人们阅读代码时,阅读过多行数后,突然发现一个上下文不理解的词,再去找定义,导致可读性差。


同时,注意例子中的空行的使用。一个是函数之间的空行,另一个是函数内的空行:在函数里干了3件事:异常判断;累加age;返回。在这3者之间添加空行,可以增加可读性。


2.2.1 上下文是关键


以上强调的原则需要在上下文中去实际判断才行,万事无绝对。

Golang 编码技巧分享(上)_第2张图片


相比,显然使用oid命名更具备可读性,而使用短变量o则不容易理解。


2.3 变量的命名不要携带变量的类型


因为golang 是一个强类型的语言,在变量的命名中包含类型是信息冗余的,而且容易导致误解错误。举个作者的例子:

var usersMap map[string]*User

我们将一个从string 到 User 的map结构,命名为UsersMap,看起来合情合理,但是变量的类型中已经包含了map,没有必要再在变量中注明了。


作者的话来讲:如果Users 描述不清楚,nameUsersMap也不见得多清楚。


对于函数的名称同样适用,比如:

Golang 编码技巧分享(上)_第3张图片

config 的名称有冗余了,类型中已经说明它是一个*Config了,如果变量在函数中最后一次引用的距离足够短,那么适用简称c或者conf 会更简洁。


提示:不要让包名抢占了好的变量名。比如context这个包,如果使用func WriteLog(context context.Context, message string),那么编译的时候会报错,因为包名和变量名冲突了。所以一般使用的时候,会使用func WriteLog(ctx context.Context, message string)


2.4 使用一致的命名


尽量不要将常见的变量名,换成其他的意思,这样会造成读者的歧义。


而且对于代码中一个类型的变量,不要多次改换它的名字,尽量使用一个名字。比如对于数据库处理的变量,不要每次出现不同的名字,比如d *sql.DB,dbase *sql.DB,DB *sql.DB,最好使用惯用的,一致的名字db *sql.DB。这样你在其他的代码中,看到变量db时,也能推测到它是*sql.DB


还有一些惯用的短变量名字,这里提一下:

  • i, j, k用作循环中的索引

  • n 用在计数和累加

  • v 表示值

  • k表示一个map或者slice 的key

  • s 表示字符串


2.5 使用一致的声明类型


对于一个变量的声明有多重声明类型:

  • var x int = 1

  • var x = 1

  • var x int;x=1

  • var x = int(1)

  • x:=1


在作者看来,这是go的设计者犯的错误,但是来不及改正了,新的版本要保持向前兼容。有这么多种声明的方式,我们怎么选择自己的类型呢。


作者给出了这些建议:


  • 当声明一个变量,但是不去初始化时,使用var


Golang 编码技巧分享(上)_第4张图片

var 往往表示这是这个类型的空值。


  • 当声明并且初始化值的时候,使用:=

  

Golang 编码技巧分享(上)_第5张图片

对于go来说,= 右侧的类型,就是=左侧的类型,上面三个例子中,最后一个使用:=的例子,既能充分标识类型,又足够简洁。


2.6 作为团队的一员


编程生涯大部分时间都是和作为团队的一员,参与其中。作者建议大家最好保持团队原来的编码风格,即使那不是你偏爱的风格。要不人会导致整个工程风格不一致,这会更糟糕。


3. 注释


注释很重要,注释应该做到以下3点之一:


  1. 解释做了什么

  2. 解释怎么做

  3. 解释为什么这么做


举个例子

这是适合对外方法的注释,解释了做了什么,怎么做的

Golang 编码技巧分享(上)_第6张图片

这是适合方法内的注释,解释了做了什么


Golang 编码技巧分享(上)_第7张图片

解释为什么的注释比较少见,但是也是必要的,比如以下:

Golang 编码技巧分享(上)_第8张图片

将value 设置成0的作用并不好理解,增加注释大大增加可理解性。


3.1 变量和常量的注释应该描述他们的内容,而不是他们的作用


在上文中提到,变量和常量的名字又应该描述他们的目的。然而他们的注释最好描述他们的内容。

const randomNumber = 6 // determined from an unbiased die

在这个例子中,注释描述了为什么randomNumber 被赋值为6,注释没有描述在哪里randomNumer会被使用。再看一些例子:

Golang 编码技巧分享(上)_第9张图片


这里区分一下,内容表示100代表什么,代表RFC 7231,但是100的目的是表示StatusContinue。


提示,对于没有初始值的变量,注释应该描述谁来初始化这些变量

// sizeCalculationDisabled indicates whether it is safe
// to calculate Types' widths and alignments. See dowidth.
var sizeCalculationDisabled bool


3.2 要对公共的名称添加文档


因为dodoc 是你的项目package的文档,所以你应该在每个公共的名称上添加注释,包括变量,常量,函数,方法。


这里给出两个谷歌风格指南的准则:


  • 任何不是简练清晰的公共的函数,都应该添加注释

  • 库中的任何函数,不管名称多长或者多么负责,都必须增加注释


举个例子:

Golang 编码技巧分享(上)_第10张图片

这个规则有一个例外,无需对实现接口的方法添加文档注释,比如不要这么做:

640?wx_fmt=png

这里给出一个io包的完整例子:

Golang 编码技巧分享(上)_第11张图片

提示:在写函数的内容前,最好先把函数的注释写出


3.2.1 不要在不完善的代码上写注释,而是重新它


如果遇到了不完善的代码,应该记录一个issue,以便后续去修复。

传统的方法是在代码上记录一个todo,以便提醒。比如

640?wx_fmt=png

3.2.2 如果要在一段代码上添加注释,要想想能否重构它


好的代码本身就是注释。如果要在一段代码上添加注释,要问问自己,能否优化这段代码,而不用添加注释。


函数应该只做一件事,如果你发现要在这个函数的注释里,提到其他函数,那么该想想拆解这个冗余的函数。


此外,函数越精简,越便于测试。而且函数名本身就是最好的注释。


4. package设计


每个go 的package 实际上都是自己的小型go程序。就好比一个function或者method的实现对调用者无关一样,包内的对外暴露的function,method和类型的实现,和调用者无关。


一个好的go长须应该努力降低耦合度,这样随着项目的演化,一个package的变化不会影响到整个程序的其他package。


接下来会讨论如何设计一个package,包括名字,类型,和编写method和funciton的一些技巧。


4.1 一个好的packag首先有一个好名字


package 的名字应该尽量简短,最好用一个单词表示。考虑package名字的时候,不要想着我要在package内写哪些类型,而是想着这个package要提供哪些服务。要以package提供哪些服务命名。


4.1.1 一个好的package名字应该是唯一的


一个项目那的package名字应该都是不同的。如果你发现可能要取相同的pcakge名字,那么可能是以下原因:


  1. package的名字太通用了

  2. 这个package提供的服务与另一个package重合了。如果是这种情况,要考虑你的package设计了


4.2 package名字避免使用base,common,util


如果package内包含了一些列不相关的function,那么很难说明这个package提供了哪些服务。这常常会导致package名字取一些通用的名字,类似utilities


大的项目中,经常会出现像utils或者helpers这样的package名字。它们往往在依赖的最底层,以避免循环导入问题。但是这样也导致出现一些通用的包名称,并且体现不出包的用意。


作者的建议是将utilshelpers这样的package名字取取消掉:分析函数被调用的场景,如果可能的话,将函数转移到调用者的package内,即使这涉及一些代码的拷贝。


提示:代码重复,比错误的抽象,代价更低


提示:使用单词的复数命名通用的包。比如strings包含了string处理的通用函数。


我们应该尽可能的减少package的数量,比如现在有三个包commonclientserver,我们可以将其组合为一个包het/http,用client.go和server.go来区分client和server,避免引入过多的冗余包。


提示,标识符的名字包含了包名,比如net/httpGETfunction,调用的使用写作http.Get,在标识符起名和package起名时要考虑这一点


未完待续,下周一继续给大家分享!




活动: Gopher Meetup 巡回第五站 - 广州报名火热进行中


详情点击阅读原文


你可能感兴趣的:(Golang 编码技巧分享(上))