Java学渣学习Go的编码规范和性能调优实战 | 青训营笔记

127 阅读8分钟

Java学渣学习go的编码规范和性能调优 | 青训营笔记

前言

这是我参加参加字节跳动第六届青训营所做的第三篇笔记,我们来学一下go的编码规范和性能调优

编码规范

代码格式

可以使用Go语言的两个工具,gofmt和goimports,来自动格式化代码。这些工具可以帮助我们遵循Go语言的代码风格规范,并保持代码的统一和可读性。

gofmt是Go语言官方提供的格式化工具。它会根据官方的代码风格规范,自动调整代码的缩进、空格、换行等格式,使代码保持一致而整洁。我们可以通过在终端中运行以下命令来使用gofmt格式化代码:

gofmt -w <文件或目录>
复制代码

其中,-w选项表示将格式化后的代码写回到原文件中,而不是仅在终端中显示格式化后的代码。

另一个工具是goimports,它是在gofmt的基础上添加了导入包路径的自动补全和优化功能。在使用goimports前,我们需要先安装它:

go get golang.org/x/tools/cmd/goimports
复制代码

安装完成后,我们就可以像使用gofmt一样使用goimports来格式化代码。同样,我们可以在终端中运行以下命令:

goimports -w <文件或目录>
复制代码

除了手动运行这些工具之外,我们还可以在GoLand中进行配置,以便在保存文件时自动进行代码格式化和导入优化。可以在Settings - Tools - Actions on Save中打开Reformat code和Optimize imports选项。这样,每次保存文件时,GoLand都会自动使用gofmt和goimports来格式化和优化代码。

在GoLand中,还可以使用Ctrl + Alt + L快捷键来手动触发对代码的格式化。这个快捷键是GoLand和其他JetBrains系列IDE中通用的格式化代码的快捷方式。

通过使用这些工具和配置,我们可以方便地保持代码的一致性和可读性,遵循Go语言的代码风格规范,并提高代码质量。

注释

好的代码注释应包括以下几个方面:

  1. 解释代码的作用:简明扼要地描述代码的功能和用途,说明代码解决了什么问题或实现了什么功能。
  2. 解释代码的实现方式:提供与代码逻辑相关的描述,说明代码是如何实现其功能的。可以涉及算法、数据结构、设计模式等方面的说明。
  3. 解释代码的原因:解释为什么选择了当前的实现方式,可以阐述一些设计决策、权衡考虑、性能优化等相关信息,帮助读者理解代码的设计思路。
  4. 解释可能出现错误的情况:指出可能的错误点、边界条件、异常情况等,帮助其他开发人员理解和处理可能出现的问题。
  5. 对公共符号进行注释:对公共函数、常量、结构体等进行注释,说明其用途、参数、返回值等信息。而对于实现接口的方法,可以根据需要进行注释。

综上所述,好的代码注释应该简洁明了,能够帮助其他开发人员理解代码的功能、实现、原因和可能出现的错误。注释应该准确而清晰地描述代码的含义和作用,以提高代码的可读性和可维护性。注释应该遵循统一的风格和规范,使整个代码库的注释具有一致性,便于团队合作和代码维护。

相比之下,Java的注释则会比Go的注释更好用。

在文档生成方面。Java中的注释可以通过工具生成API文档,并能够提供更丰富的文档内容,给开发人员更全面的了解和使用代码的指导。

在Java中,可以使用Javadoc工具生成代码的文档。Javadoc支持从注释中提取信息,并生成可读性高的文档。通过使用一些特定的标签(如@param、@return、@throws等),Javadoc工具能够自动生成方法的参数、返回值、异常信息等文档内容。这样,开发人员可以从生成的文档中快速获取代码的信息,了解如何正确使用和调用代码。

相比之下,Go的注释功能相对简单,并没有像Java的Javadoc那样强大的文档生成能力。Go的注释主要用于提供简短的解释和备注,注重于代码的概要说明和关键信息。Go的注释通常用于标识包、类型、函数、方法等的名称和功能,但在文档生成方面的功能较为有限。

尽管Java的注释在文档生成方面更强大,但这并不意味着Go的注释无用。Go仍然鼓励开发人员编写注释来解释代码的逻辑、目的和使用方式。虽然不像Javadoc那样可以自动生成详尽的文档,但通过良好的注释习惯,可以使代码更易于理解和维护,并帮助其他开发人员快速上手和使用代码。

总而言之,Java的注释在文档生成方面更强大,但Go的注释仍然在代码解释和理解方面有着重要的作用。选择何种语言的注释,应根据具体的需求场景和开发者团队的偏好来决定。

命名规范

变量
  1. 简洁胜于冗长。
  2. 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写(例如使用ServerHTTP而不是ServerHttp,使用XMLHTTPRequest或是xmlHTTPRequest而不是XmlHttpRequest)。
  3. 变量距离其被使用的地方越远,则需要提供更多的上下文信息,尤其是对于全局变量。
// Bad
for index := 0; index < len(s); index++ {
    // do sth.
}
 
// Good
for i := 0; i < len(s); i++ {
    // do sth.
}
复制代码

由于 index 变量作用域仅限于 for 循环内,更长的注释反而对增加阅读的理解没什么作用,所以采用更短的 i 变量。

// Good
func (c *Client) send(req *Request, deadline time.Time)
 
// Bad
func (c *Client) send(req *Request, t time.Time)
复制代码

将 deadline 换成 t 降低了变量名的信息量。前者特指截止时间,而后者仅是代表某个时间。(一般情况下,t即代表时间)

函数
  1. 函数名不应携带包名的上下文信息,因为两者总是成对出现。
  2. 函数名应当尽量简短。
  3. 当名为foo包的某个函数返回类型为Foo时,可以省略类型信息而不导致歧义。
  4. 当名为foo包的某个函数返回类型为T而不是Foo时,应该在函数名中加入类型信息。
package http
 
// Bad
func ServeHTTP(I net.Listener, handler Handler) error
 
// Good
func Serve(I net.Listener, handler Handler) error
复制代码
  1. Go的函数名应仅由小写字母组成,避免使用大写字母和下划线等特殊字符。
  2. 函数名应尽量简短,同时具备一定的上下文信息。
  3. 避免与标准库同名。
  4. 避免使用常见的变量名作为包名,例如使用bufio而不是buf。
  5. 使用单数形式而非复数形式,例如使用encoding而不是encodings。
  6. 使用缩写时要慎重,确保不影响上下文意义。例如,使用fmt比fotmat更简短。 总的来说,Go的包命名规范体现了简洁至上的风格。

编码规范

控制流程
// Bad
if foo {
    return x
} else {
    return nil
}
 
// Good
if foo {
    return x
}
return nil
复制代码
  1. 避免嵌套,保持正常流程清晰
  2. 尽量保持正常代码路径为最小缩进
错误和异常处理
  1. 对于简单错误(仅出现一次,在其他地方不需要捕获),优先使用errors.New创建匿名变量表示错误;
  2. 如果需要格式化输出错误信息,使用fmt.Errorf
  3. 使用%w将错误关联至错误链中,通过errors.Is可以判断错误链上是否含有特定错误;
  4. 使用errors.As获取错误链上特定种类的错误;
  5. 只有在程序启动阶段发生不可逆转的错误时才使用panic,配合recover语句可以从panic中恢复; 总的来说,以上规范确保了错误处理的简洁和规范性。

性能优化

  1. 进行基准测试时,请使用Benchmark
  2. slicemap预分配内存时,通过在make时指定容量信息来实现;
  3. 切片的创建切片不会导致新的底层数组的创建,但可能导致内存泄漏,此时应使用copy而非重新切片;
  4. 多个字符串拼接时,使用strings.Builder比直接使用+bytes.Buffer更快;
  5. 需要占位符时,可以使用空结构体struct{}代替,它不会占据内存空间;
  6. 使用atomic包代替锁修改变量;

引用

这篇笔记主要内容来源于: 后端 - 字节内部课 (juejin.cn)