golang接收器命名规范想法和团队实践

1,724 阅读4分钟

说明

go在代码格式规范上做了减法,通过原生工具fmt获得统一格式的代码,降低compare、review心智。这种面向工程的设计使团队被统一规范,减少了制定和监督规范所需要付出工作。

go官方wiki里建议了一些code review规范,大部分很不错,也有少数在我的团队实践中并不理想,接收器的命名就是其中之一。

官方接收器的命名规范

官方的code review中对接收器的命名提供了参考规范。内容如下

Receiver Names
The name of a method's receiver should be a reflection of its identity; often a one or two letter abbreviation of its type suffices (such as "c" or "cl" for "Client"). Don't use generic names such as "me", "this" or "self", identifiers typical of object-oriented languages that gives the method a special meaning. In Go, the receiver of a method is just another parameter and therefore, should be named accordingly. The name need not be as descriptive as that of a method argument, as its role is obvious and serves no documentary purpose. It can be very short as it will appear on almost every line of every method of the type; familiarity admits brevity. Be consistent, too: if you call the receiver "c" in one method, don't call it "cl" in another. 原文链接

理解成中文如下:

  1. 命名要反应其身份。
  2. 不用 me this self,因为这些是其它语言中的关键字,且含义与go设计不一致(go接收器是做作为函数的一个参数来设计)。
  3. 接收器名字尽量简短,因为会有很多地方使用。
  4. 同一个接收器在不同的方法中命名要一样。不能一个取c一个方法定义为cl。

以上,个人非常同意第1、3、4点,明确统一的含义易于阅读维护。

关于第2点,能理解也非常了解go接受器是做作为函数的一个参数来设计(本质上最后接收器和函数地址都进systemstack被执行而已)。

不同意观点

个人不同意的第2点是在因为别的语言设计这些关键字有其它含义,go接收器跟其它语言中 this、me、self 的意思不一样

  1. golang说接收器只是参数语义,而不是其它语言那样代表函数所属,但是使用起来可太像了。看着像猪,走起来像猪、吃起来像猪,就认为是猪呗。 我认为完全不管其它语言,在go里给this、me约定下新的含义即可。

  2. 第1、3、4点建议命名要明确、简短、统一
    没有比this、me、self、my这样更明确、简短、统一得了。来看两个反面例子:
    2.1反面例子1
    golang buffer源码中摘抄一段,在ide看代码一般是30~50行,只要代码稍长翻到下面,很容易迷失b是?,没有达到明确目的。

  m := b.Len()
  // If buffer is empty, reset to recover space.
  if m == 0 && b.off != 0 {
  	b.Reset()
  }
  // Try to grow by means of a reslice.
  if i, ok := b.tryGrowByReslice(n); ok {
  	return i
  }
  if b.buf == nil && n <= smallBufferSize {
  	b.buf = make([]byte, n, smallBufferSize)
  	return 0
  }
  c := cap(b.buf)
  if n <= c/2-m {
  	// We can slide things down instead of allocating a new
  	// slice. We only need m+n <= c to slide, but
  	// we instead let capacity get twice as large so we
  	// don't spend all our time copying.
  	copy(b.buf, b.buf[b.off:])
  } else if c > maxInt-c-n {
  	panic(ErrTooLarge)
  } else {
  	// Not enough space anywhere, we need to allocate.
  	buf := makeSlice(2*c + n)
  	copy(buf, b.buf[b.off:])
  	b.buf = buf
  }
  // Restore b.off and len(b.buf).
  b.off = 0
  b.buf = b.buf[:m+n]
  return m

2.2 反面例子2
我们的业务代码这也是写本文的原因。api接口一个账号管理的源码,因业务稍微比较复杂,但函数方法稍长ps:当然有办法拆分函数大小,这对编码规范要求更高,实际更难实践

func (a *Account) PostAccounts(ctx *fasthttp.RequestCtx, url string) {
...很长一屏放不下...
  err = a.accountRedis.RedisSAdd(key, accountIdArr...)
...很长一屏放不下...
}

看到a.accountRedis直接干懵了。按照习惯作用域越短命名越短,这种应该是一个临时变量来着。 这时肯定有人问了,把名字改长不就解决

func (acct *Account) PostAccounts(ctx *fasthttp.RequestCtx, url string) {
  err = acct.accountRedis.RedisSAdd(key, accountIdArr...)
}

这样当然可以,还可以为receiver的缩写名准备一份参考规范例如Bill-->bill而不能用b User-->user或者usr。但是,真的定规范就能解决吗?。

  1. 定规范不容易,定易遵守的规范更难。
    第2点可以通过指定规范解决。但规范需要人遵守,代码好说,人事难办。命名这语言类非常个性化,难有自动化工具辅助。
    另外,实际工作中会不会review?或者会不会高质量的review?或者为了保质review是否得再定规范[禁止套娃]?嗯,自行体会。

团队实践的方案

最终规范:指针用this 非指针用me
忘掉其它语言的定义,就这么定了,不再需要为命名查单词缩写,不用再约定长篇接收器命名规范,仅仅使用工具lint就能进行review,就是这样直接。

样例:

func (this *Account) DoB(this.yyy)
func (me Account) DoA(me.xxx)

最后

如果方法接收者名字是 self me this 类似的词,Goland IDE会提示Receiver has generic name信息 如下图所示

解决办法