高质量编程&编码规范要点梳理 | 豆包MarsCode AI刷题

101 阅读7分钟

正确性是基础,而高质量是追求。

上面这句话显然是不言而喻的。那么问题来了,我们要追求高质量的编程,而究竟何谓高质量的代码,又有什么注意规范?这篇笔记将根据青训营课程中高质量编程的课程,结合自身的经历与感悟,为大家总结梳理一下常见的高质量编程规范与要点。

高质量编程概览

正如我们开篇所给出的一样,一言以蔽之,高质量编程就是指我们编写的代码能够达到正确可靠、简洁清晰的目标。究其本身,又有几个要点:

  1. 各种边界情况是否考虑完备
  2. 异常情况处理,稳定性保证
  3. 易读易维护

边界情况

提到对于边界情况的警惕,在初学编程的过程中遇到最多的情况就是各种越界问题。就拿C++举例,如果使用vector数组越界好在还会发出错误警告提醒你“有地方越界了,快去修bug!”。但更不巧的是如果使用传统C数组,越界情况不会被告知,如果恰巧修改到某个危险区域,就可能引发程序崩溃等严重问题。

一个更加经典的例子就是C++的gets()函数被弃用并删除,因为其机制原因,不检查并限制输入长度可能会使缓冲区边界溢出,引发安全漏洞。

异常情况

这就又涉及到一个经典的程序员笑话:酒吧炒饭问题。

酒吧本应该是点酒品的场所,但当面对点各种稀奇古怪的东西甚至匪夷所思的操作的时候,我们编写的代码应当对这些预期的或者非预期的异常情况进行处理,使其能够回报错误信息,同时不至于崩溃。

易读易维护

“我生平最讨厌的只有两件事:别人的代码不写注释、我写代码要写注释”。代码写完之后不仅仅只是拿来运行的,也是拿来看的。好的、规范的注释可以为他人(以及未来的自己)提供快速理解代码功能的可能。提高团队协作的效率。

当然代码本身的结构也要在保证功能的同时尽量做到简洁清晰,不需要过多奇技淫巧。过多高级功能换来的不一定是性能的飞跃,也有可能是维护的昏天黑地。

代码保持格式化

代码保持格式化能够让人阅读起来更加轻松,提高阅读效率,降低理解成本。

Go语言常用的代码自动格式化工具有两个:gofmt和goimports。都是Go语言官方出品的工具。

gofmt可以将我们所编写的代码自动进行格式化成官方推荐的规范格式。而goimports可以根据代码内容自动修改import部分的内容。

GoLand和安装了Go插件的Vscode应该都带有这两个工具且默认启用。

注释规范

前面提到了注释在我们编写高质量代码的过程中具有重要的作用。那么下面是一些对于注释应当达成的规范要求。

注释应该解释代码的作用

在公共符号的开头部分添加注释,解释该公共符号的作用。

公共符号:变量、常量、函数以及结构体等。

注释应该解释代码如何做的

  • 对于并非非常简短或者明显的公共功能实现要加以注释。
    • 公共功能课程中没有提及是什么,猜测是一些比较复杂的高级语法之类不太容易第一眼看明白的内容?
  • 对于所有函数必须加以注释。

注释应该解释代码实现的原因

对于一些不明显的写法,需要在注释中写明这一部分为什么这样做(可能的外部原因),同时提供一些上下文信息,方便出现问题时的快速定位。

注释应该解释代码什么情况会出错

Go语言的一大特点就是错误处理的代码量非常显著,所以注释中需要对代码的限制条件进行必要的解释。当然其他语言开发时也必须进行相应的注释,只是Go语言在这里适合作为例子而已。

命名规范

对于变量

  • 简洁胜于冗长
  • 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
    • 例如使用ServeHTTP而不是ServeHttp
    • 使用XMLHTTPRequest或者xmlHTTPRequest
  • 变量距离其被使用的地方越远,则需要携带越多的上下文信息
  • 全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义

对于函数

  • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
  • 函数名尽量简短
  • 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
  • 当名为foo的包某个函数返回类型T时(T并不是Foo),可以在函数名中加入类型信息

对于包名

  • 只由小写字母组成。不包含大写字母和下划线等字符
  • 简短并包含一定的上下文信息。例如schema、task 等
  • 不要与标准库同名。例如不要使用sync或者strings

以下规则尽量满足,以Go语言的标准库包名为例

  • 不使用常用变量名作为包名。例如使用bufio而不是buf
  • 使用单数而不是复数。例如使用encoding 而不是encodings
  • 谨慎地使用缩写。例如使用fmt在不破坏上下文的情况下比 format 更加简短

流程规范

避免嵌套,保持正常流程清晰

对于程序员来说,最典型的屎山代码结构就是各种复杂的嵌套语句套来套去,时不时跳出一下跳到十万八千里外可能另外一个文件中。

因此要实现规范的编码流程,应当在开发时就有意的去避免过于复杂的嵌套逻辑的实现。必要时抽象解耦分层处理。这样也可以减少一段代码中过多的变量堆积,在阅读时挤占脑容量容易逻辑不清的问题。

尽量保持正常代码路径为最小缩进

优先处理错误情况/特殊情况,而将主代码逻辑放在特判的外面,减少主要逻辑的缩进级别。

函数的执行尽量顺着屏幕向下直线行进,便于提升代码的可读性和可维护性。

错误、异常处理

简单错误

对于仅仅出现一次,且不需要在其他地方捕获并处理的错误,称为简单错误。针对简单错误,我们可以:

  • 优先使用errors.New()方法来直接表示出来简单错误。
  • 如果对于错误有格式化的需求,可以使用fmt.Errorf()

错误链

Go语言提供了两个方法error.Wrap()error.Unwrap()来用于将错误嵌套成链式结构,前者用于封装错误链,后者用于解封链提取错误。

fmt.Errorf中也可以使用%w关键字来将错误关联到错误链中。

错误判定

  • 判定一个错误是否为特定错误,使用error.Is()
  • 该方法不同于使用==直接判断,这样可以追踪到错误链上是否含有某个特定的错误。
  • 如果需要获取到特定某种错误,可以使用error.As()

panic与recover

panic与recover方法一般需要成对出现。在出现异常的时候使用panic方法调用特定的错误处理代码,在这段代码中需要有recover方法提供错误恢复。recover方法需要在defer函数中使用。

正因这种成对性,在没有recover的情况下单独使用panic会造成程序崩溃。所以一般情况下非必要可以直接使用error代替panic,可以一定程度上降低错误处理的复杂度。

小结

通过高质量编程规范课程的学习,以及时隔几天后这篇笔记的再次梳理,我对包括Go语言在内其实是普适的编程规范有了更深的了解。在今后的编码中,尤其是团队协作开发任务中也会尽量尝试去遵守、实践学到的内容。