高质量编程是指以高标准和良好实践来编写可读、可维护、可测试和性能等方面的优秀表现的代码。
从指令的角度考虑,开发中应如何编码,才能减少执行的指令。各种语言特性和语法各不相同,但高质量编程遵循的原则是一致的,如下:
gofmt
gofmt
是Go语言官方提供的一个命令行工具,用于格式化Go代码。它会自动调整代码的缩进、空格、括号位置等,以确保代码的一致性和可读性。
在命令行中,可以使用以下命令来运行gofmt
工具:
gofmt -w <文件或目录>
其中,-w
选项表示将格式化后的代码直接写回源文件,如果不使用-w
选项,则gofmt
会将格式化后的代码输出到标准输出。
例如,要格式化名为main.go
的文件,可以运行以下命令:
gofmt -w main.go
如果要格式化整个项目目录下的所有Go文件,可以运行以下命令:
gofmt -w .
需要注意的是,gofmt
工具会直接修改源文件,因此在运行之前,建议先备份代码,以防止意外修改。
此外,还可以使用一些编辑器或IDE中的插件,如GoLand、Visual Studio Code的Go插件等,来自动触发gofmt
工具的格式化操作。这样可以在保存文件时自动进行代码格式化,进一步提高开发效率。
goimports
goimports
也是一个Go语言官方提供的工具,它是在 gofmt
的基础上增加了自动导入功能。除了格式化代码外,goimports
还会自动检测并添加缺失的导入语句,删除未使用的导入语句,并按照一定的规则对导入语句进行排序并分类。
在Go语言中,注释是用来对代码进行说明和解释的文本。Go语言支持两种类型的注释:单行注释和多行注释。
//
开头,用于注释单行代码或单行说明。// 这是一个单行注释
fmt.Println("Hello, World!") // 打印Hello, World!
/*
开头,以*/
结尾,用于注释多行代码或多行说明。/*
这是一个多行注释,
可以跨越多行。
*/
fmt.Println("Hello, World!")
文档注释以/*
开头,以*/
结尾,并且在每行注释前添加一个*
。文档注释可以包含一些特殊的标记,如@param
、@return
等,用于描述函数的参数和返回值。
/*
calculateSum函数用于计算两个整数的和。
@param a 第一个整数
@param b 第二个整数
@return 两个整数的和
*/
func calculateSum(a, b int) int {
return a + b
}
可以使用go doc
命令来查看代码中的文档注释。
go doc <包名>.<函数名>
例如,要查看calculateSum
函数的文档注释,可以运行以下命令:
go doc <包名>.calculateSum
注释是编写清晰、易读的代码的重要组成部分。良好的注释可以帮助其他开发人员理解代码的意图和功能,并且可以用来生成文档以供参考。因此,在编写代码时,建议使用注释来解释和说明代码的逻辑和功能。
命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性.
Go在命名时以字母a到Z或a到Z或下划线开头,后面跟着零或更多的字母、下划线和数字(0到9)。Go不允许在命名时中使用@、$和%等标点符号。
和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写,但遇到特有名词时,需要遵循以下规则:
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool
命名规则基本和上面的结构体类型。 单个函数的结构名以 “er” 作为后缀,例如 Reader , Writer 。
type Reader interface {
Read(p []byte) (n int, err error)
}
尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词。
my_test.go
保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。
package demo
package main
流程控制是每种编程语言控制逻辑走向和执行次序的重要部分。
Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。
Go语言if else(分支结构)
在Go语言中,关键字 if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号{}括起来的代码块,否则就忽略该代码块继续执行后续的代码。
if condition {
// do something
}
如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行,if 和 else 后的两个代码块是相互独立的分支,只能执行其中一个。
if condition {
// do something
} else {
// do something
}
如果存在第三个分支,则可以使用下面这种三个独立分支的形式:
if condition1 {
// do something
} else if condition2 {
// do something
} else {
// catch-all or default
}
else if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else if 结构,如果必须使用这种形式,则尽可能把先满足的条件放在前面。
Go语言switch case语句
表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true 进行匹配。
Go语言改进了 switch 的语法设计,case 与 case 之间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行,示例代码如下:
var a = "hello"
switch a {
case "hello":
fmt.Println(1)
case "world":
fmt.Println(2)
default:
fmt.Println(0)}
//代码输出: 1
上面例子中,每一个 case 均是字符串格式,且使用了 default 分支,Go语言规定每个 switch 只能有一个 default 分支。
Go语言for循环结构
使用循环语句时,需要注意的有以下几点:
{
必须与 for 处于同一行。for j := 0; j < 5; j++ {
for i := 0; i < 10; i++ {
if i > 5 {
break JLoop
}
fmt.Println(i)
}
}
JLoop:// ...
上述代码中,break 语句终止的是 JLoop 标签处的外层循环。
当涉及到错误和异常处理时,Go语言采用了一种不同于其他语言的方法。Go语言中的错误处理是通过返回错误值来完成的,而不是使用异常机制。
errors.New()
函数用于创建一个新的错误对象。它接收一个字符串参数作为错误的描述信息,并返回一个错误类型的值。通过判断错误对象实例来确定具体错误类型。err := errors.New("something error")
fmt.Errorf()
创建 error 接口错误对象err := fmt.Errorf("发生了错误:%s", reason)
通过调用 fmt.Printf 函数,并给定占位符 %s 就可以打印出某个值的字符串表示形式。对于其他类型的值来说,只要我们能为这个类型编写一个 String 方法,就可以自定义它的字符串表示形式。
而对于 error 类型值,它的字符串表示形式则取决于它的 Error 方法。在上述情况下,fmt.Printf 函数如果发现被打印的值是一个 error 类型的值,那么就会去调用它的 Error 方法。fmt 包中的这类打印函数其实都是这么做的。
顺便提一句,当我们想通过模板化的方式生成错误信息,并得到错误值时,可以使用 fmt.Errorf 函数。该函数所做的其实就是先调用 fmt.Sprintf 函数,得到确切的错误信息;再调用 errors.New 函数,得到包含该错误信息的 error 类型值,最后返回该值。
在Go语言中,标准库中的errors包提供了Wrap
和Unwrap
函数,是指错误处理机制中的用于错误的包装和解包。
目前Go标准库中提供的用于wrap error的API有fmt.Errorf和errors.Join。fmt.Errorf最常用,fmt.Errorf也支持通过多个%w一次打包多个error,下面是一个完整的例子:
func main() {
err1 := errors.New("error1")
err2 := errors.New("error2")
err3 := errors.New("error3")
err := fmt.Errorf("wrap multiple error: %w, %w, %w", err1, err2, err3)
fmt.Println(err)
e, ok := err.(interface{ Unwrap() []error })
if !ok {
fmt.Println("not imple Unwrap []error")
return
}
fmt.Println(e.Unwrap())
}
示例运行输出如下:
wrap multiple error: error1, error2, error3
[error1 error2 error3]
我们看到,通过fmt.Errorf一次wrap的多个error在String化后,是在一行输出的。
errors.Join
用于将一组errors wrap为一个error。 下面是用errors.Join一次打包多个error的示例:
func main() {
err1 := errors.New("error1")
err2 := errors.New("error2")
err3 := errors.New("error3")
err := errors.Join(err1, err2, err3)
fmt.Println(err)
errs, ok := err.(interface{ Unwrap() []error })
if !ok {
fmt.Println("not imple Unwrap []error")
return
}
fmt.Println(errs.Unwrap())
}
这个示例输出如下:
$go run demo2.go
error1
error2
error3
[error1 error2 error3]
我们看到,通过errors.Join一次wrap的多个error在String化后,每个错误单独占一行。
errors.Is(err, target)
:判断err
是否是target
类型的错误,返回布尔值。这个函数用于判断错误类型是否匹配。 if errors.Is(err, io.EOF) {
fmt.Println("遇到了文件末尾")
}
errors.As(err, target)
:将err
转换为target
类型的错误,返回布尔值。这个函数用于将错误转换为特定类型的错误,并进行相应的处理。 var n *net.OpError
if errors.As(err, &n) {
fmt.Println("遇到了网络错误:", n)
}
panic:
1、内建函数
2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer 有点类似 try-catch-finally 中的 finally
4、直到goroutine整个退出,并报告错误
recover:
1、内建函数
2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
3、一般的调用建议
a). 在defer函数中,通过recever来终止一个gojroutine的panicking过程,从而恢复正常代码的执行
b). 可以获取通过panic传递的error
简单来讲:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。