[3分钟] GO:如何设计友好的标识符

发布时间 2023-12-07 15:31:40作者: 姜七七

二、如何编写优秀的注释

Good code has lots of comments, bad code requires lots of comments. (好的代码有很多注释,坏代码需要很多注释。) — Dave Thomas and Andrew Hunt (The Pragmatic Programmer)

2.1 思考

2.1.1 什么是好的注释

简洁;有效;思路清晰;描述简单易懂。

2.1.2 注释的作用是什么?

1)解释变量的含义

2)解释函数的作用

  • 2.1) 对入参、返回值做出说明,增强可读性

  • 2.2) 解释函数的步骤,即函数是如何实现的;

2.2 如何写出好的注释

注释是影响代码可读性的一大因素。注释应该做的三件事中的一件,而且只做一件

  1. The comment should explain what the thing does(注释应该解释具体做什么)
  2. The comment should explain how the thing does what it does(注释应该解释是如何做的)
  3. The comment should explain why the thing is why it is(注释应该解释为什么是这样的)

第一种形式的完美应用是公共符号的注释

// Open opens the named file for reading.
// If successful, methods on the returned file can be used for reading.

第二种形式非常适合在方法中的注释

// queue all dependant actions
var results []chan error
for _, dep := range a.Deps {
results = append(results, execute(seen, dep))
}

第三种形式是为了在代码中交代上下文,交代代码的外部因素。即解释为什么是这样的

return &v2.Cluster_CommonLbConfig{
// Disable HealthyPanicThreshold
HealthyPanicThreshold: &envoy_type.Percent{
Value: 0,
},
}

在此示例中,无法清楚地明白 HealthyPanicThreshold 设置为零百分比的效果。 需要注释0值将禁用 panic 阈值。

2.2.1 变量和常量的注释应描述其内容而非其目的

我之前说过,变量或常量的名称应该描述他的作用。当你为变量或常量添加一个注释时,应该描述他的内容,而不是他的作用。

const randomNumber = 6 // determined from an unbiased die

上面例子中,描述了变量randomNumber为什么是6,以及6从哪来,而不是描述randomNumber在哪里使用。还有一些例子如下:

const (
StatusContinue = 100           // RFC 7231, 6.2.1
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
StatusProcessing = 102 // RFC 2518, 10.1

StatusOK = 200 // RFC 7231, 6.3.1

在HTTP的上下文中,数字 100 被称为 StatusContinue,如 RFC 7231 第 6.2.1 节中所定义。

贴士: 对于没有初始值的变量,注释应描述谁负责初始化此变量

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

这里的注释让读者知道 dowidth 函数负责维护 sizeCalculationDisabled 的状态。

隐藏在众目睽睽下 这个提示来自Kate Gregory[3]。有时你会发现一个更好的变量名称隐藏在注释中。

// registry of SQL drivers
var registry = make(map[string]*sql.Driver)

注释是由作者添加的,因为 registry 没有充分解释其目的 - 它是一个注册表,但注册的是什么?

通过将变量重命名为 sqlDrivers,现在可以清楚地知道此变量的目的是保存SQL驱动程序。

var sqlDrivers = make(map[string]*sql.Driver)

之前的注释就是多余的,可以删除。

2.2.2 公共符号始终要注释

因为 godoc 是你的包的文档,所以应该为包中声明的每个公共标识符 —​ 变量、常量、函数以及公共的方法添加注释。

这里是两条Google Style

  • 任何不简短且不清晰公共函数都需要注释
  • 不管长度或者复杂度如何,库中的函数都必须注释

这条规则有一个例外; 您不需要注释实现接口的方法。 具体不要像下面这样做:

// Read implements the io.Reader interface
func (r *FileReader) Read(buf []byte) (int, error)

这个注释什么也没说。 它没有告诉你这个方法做了什么,更糟糕是它告诉你去看其他地方的文档。 在这种情况下,我建议完全删除该注释。

这是 io 包中的一个例子

// LimitReader returns a Reader that reads from r
// but stops with EOF after n bytes.
// The underlying implementation is a *LimitedReader.
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }

// A LimitedReader reads from R but limits the amount of
// data returned to just N bytes. Each call to Read
// updates N to reflect the new amount remaining.
// Read returns EOF when N <= 0 or when the underlying R returns EOF.
type LimitedReader struct {
R Reader // underlying reader
N int64  // max bytes remaining
}

func (l *LimitedReader) Read(p []byte) (n int, err error) {
if l.N <= 0 {
return 0, EOF
}
if int64(len(p)) > l.N {
p = p[0:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n)
return
}

请注意,LimitedReader 的声明就在使用它的函数之前,而 LimitedReader.Read 的声明遵循 LimitedReader 本身的声明。 尽管 LimitedReader.Read
本身没有文档,但它清楚地表明它是 io.Reader 的一个实现。

贴士: 在编写函数之前,请编写描述函数的注释。 如果你发现很难写出注释,那么这就表明你将要编写的代码很难理解。

2.2.3 不要注释不好的代码,将它重写

粗劣的代码的注释高亮显示是不够的。 如果你遇到其中一条注释,则应提出问题,以提醒您稍后重构。 只要技术债务数额已知,它是可以忍受的。

标准库中的惯例是注意到它的人用 TODO(username) 的样式来注释。

// TODO(dfc) this is O(N^2), find a faster way to do this.

注释 username 不是该人承诺要解决该问题,但在解决问题时他们可能是最好的人选。 其他项目使用 TODO日期问题编号来注释。

2.2.4 与其注释一段代码,不如重构它

Good code is its own best documentation. As you’re about to add a comment, ask yourself, 'How can I improve the code so that this comment isn’t needed?' Improve the code and then document it to make it even clearer. 好的代码是最好的文档。 在即将添加注释时,请问下自己,“如何改进代码以便不需要此注释?' 改进代码使其更清晰。 — Steve McConnell

函数应该只做一件事。 如果你发现自己在注释一段与函数的其余部分无关的代码,请考虑将其提取到它自己的函数中。

除了更容易理解之外,较小的函数更易于隔离测试,将代码隔离到函数中,其名称可能是所需的所有文档。

2.3 利用工具更好的编写注释

2.3.1 浅析高亮注释

在代码中,注释大部分都是灰色的。但是GoLand可以设置一些特殊的注释时高亮显示的。比如,大家熟悉的TODO,除了TODO,还有一个FIXME,这两个都是官方定义的高亮注释,大小写都不敏感。作用一般如下:

  1. TODO:一般用于逻辑或者功能待实现。
  2. FIXME:该标识说明标识处的代码是错误的,有待修正。
// todo 这里的逻辑待实现
// fixme 这里的代码错误待修复

除了官方定义的两个高亮注释,我们也可以加入自己的高亮注释。

在GoLand的 file -> settings -> editor -> todo 处可以自定义高亮注释,定制自己希望的高亮注释。

注:1、自定义高亮注释后,需要将GoLand重启来加载设置

2、可以使用正则表达式来自定义高亮注释(其实是模式。叫高亮注释更简单一些)

2.3.2 深入了解高亮注释的设置和使用

默认高亮注释 TODO、FIXME
在哪配置 Settings/Preferences -> Editor -> TODO
如何使用 View -> Tool Windows -> TODO

有时,您需要标记部分代码以供将来参考:优化和改进的地方、可能的更改要讨论的问题等等。 GoLand 允许您添加特殊类型的注释,这些注释在编辑器中突出显示、
索引并在 TODO 工具窗口中列出。这样您和您的团队成员就可以跟踪需要注意的问题。

GoLand官方提供两种模式,及大小写不敏感todofixme。你可以修改默认模式或增加自己的模式。

用法:
1. 你可以创建高亮注释:使用空格或者tab键缩进注释就可以高亮多行【任意缩进两个,即两个空格、两个tab机或者一个空格一个tab可以实现】

// todo 
//  sss 
//      sss

2. 禁用多行高亮:点击file -> setting -> editor -> todo, 取消 Treat indented text on the following lines as part of the same TODO 选项

3. 查看高亮注释:点击View -> Tool Windows -> TODO。可以在弹出的界面上选择要查看的范围,可以单击进入注释所在的文件。下图中关闭单击跳转时候,可以进行双击跳转。

4. 添加自定义的模式以及todo过滤器:

  • 添加自定义模式:这在2.3.2节我们已经讲过了。
  • 添加过滤器:过滤器的作用是当你查看高亮注释时,可以过滤你想要看的注释。我们可以使用下图中的方式添加过滤器。


内容学习于该博客:英文博客

同时借鉴于该翻译:中文翻译

高亮注释部分学习于官方文档:官方文档