Go语言规范:高质量编程及编码规范

一、简介

高质量编程是指以高标准和良好实践来编写可读、可维护、可测试和性能等方面的优秀表现的代码。

  • 各种边界条件是否考虑完备
  • 异常情况处理、稳定性保证
  • 易读易维护

(一)编码原则


从指令的角度考虑,开发中应如何编码,才能减少执行的指令。各种语言特性和语法各不相同,但高质量编程遵循的原则是一致的,如下:

  1. 简洁性:代码应该简洁明了,避免冗余和复杂的逻辑。简洁的代码更易于理解、调试和维护。
  2. 可读性:代码应该易于阅读和理解。使用有意义的变量和函数命名,遵循一致的代码风格,添加适当的注释和文档,以提高代码的可读性。
  3. 一致性:在编写代码时应遵循一致的命名规范、代码风格和代码组织结构。一致的代码风格使得代码更易于理解和维护。
  4. 模块化:将代码划分为模块或函数,每个模块或函数只负责一个明确的任务。模块化的代码更易于测试、重用和维护。
  5. 错误处理:合理处理错误和异常情况。避免忽略错误,而是采取适当的错误处理机制,例如返回错误值或抛出异常。
  6. 依赖管理:使用Go模块管理依赖项,确保代码的可重复性和可维护性。可以使用Go Modules来管理项目的依赖。
  7. 测试:编写测试是保证代码质量的重要手段。编写单元测试和集成测试,覆盖代码的各个功能和边界情况。
  8. 文档:编写清晰、准确的文档,包括代码注释、函数说明和项目文档等。良好的文档可以帮助其他开发人员理解和使用代码。
  9. 并发安全性:在多线程环境中,需要确保代码的并发安全性。可以使用Go语言提供的互斥锁(Mutex)或通道(Channel)等机制来实现。

(二)如何编写高质量的Go代码


1. 代码格式

  • 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还会自动检测并添加缺失的导入语句,删除未使用的导入语句,并按照一定的规则对导入语句进行排序并分类。

2. 注释

在Go语言中,注释是用来对代码进行说明和解释的文本。Go语言支持两种类型的注释:单行注释和多行注释。

  • 单行注释:以//开头,用于注释单行代码或单行说明。
// 这是一个单行注释
fmt.Println("Hello, World!") // 打印Hello, World!
  • 多行注释:以/*开头,以*/结尾,用于注释多行代码或多行说明。
/*
这是一个多行注释,
可以跨越多行。
*/
fmt.Println("Hello, World!")
  • 除了用来对代码进行说明,注释还可以用来生成文档。在Go语言中,可以使用特殊格式的注释来生成文档,这种注释被称为文档注释或文档注解。

文档注释以/*开头,以*/结尾,并且在每行注释前添加一个*。文档注释可以包含一些特殊的标记,如@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

注释是编写清晰、易读的代码的重要组成部分。良好的注释可以帮助其他开发人员理解代码的意图和功能,并且可以用来生成文档以供参考。因此,在编写代码时,建议使用注释来解释和说明代码的逻辑和功能。

3. 命名规范

命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性.

Go在命名时以字母a到Z或a到Z或下划线开头,后面跟着零或更多的字母、下划线和数字(0到9)。Go不允许在命名时中使用@、$和%等标点符号。

  • Go是一种区分大小写的编程语言。因此,Manpower和manpower是两个不同的命名。
  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private)
变量命名

和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写,但遇到特有名词时,需要遵循以下规则:

  • 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient
  • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID
  • 错误示例:UrlArray,应该写成 urlArray 或者 URLArray
  • 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头
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的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

package demopackage main

4. 控制流程

流程控制是每种编程语言控制逻辑走向和执行次序的重要部分。

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 处于同一行。
  • Go语言中的 for 循环与C语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
  • Go语言的 for 循环同样支持 continue 和 break 来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环,如下例:
for j := 0; j < 5; j++ {for i := 0; i < 10; i++ {if i > 5 {break JLoop        }        fmt.Println(i)}
}JLoop:// ...

上述代码中,break 语句终止的是 JLoop 标签处的外层循环。

5. 错误和异常处理

当涉及到错误和异常处理时,Go语言采用了一种不同于其他语言的方法。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 类型值,最后返回该值。

错误的Wrap和Unwrap

在Go语言中,标准库中的errors包提供了WrapUnwrap函数,是指错误处理机制中的用于错误的包装和解包。

  • 错误包装是指在处理错误时,将原始错误包装在新的错误中,以提供更多的上下文信息。这样做可以保留原始错误的堆栈信息,并将新的错误与之关联。
  • 错误解包是指从包装的错误中提取出原始错误。这样做可以在需要时获取原始错误的详细信息。

目前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化后,每个错误单独占一行。

错误判定
  1. errors.Is(err, target):判断err是否是target类型的错误,返回布尔值。这个函数用于判断错误类型是否匹配。
    if errors.Is(err, io.EOF) {fmt.Println("遇到了文件末尾")
    }
  2. errors.As(err, target):将err转换为target类型的错误,返回布尔值。这个函数用于将错误转换为特定类型的错误,并进行相应的处理。
    var n *net.OpError
    if errors.As(err, &n) {fmt.Println("遇到了网络错误:", n)
    }
异常panic和恢复recover用法

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捕获这个异常,然后正常处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/47763.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中异常的详细讲解与细节讨论

用一个代码引出异常为什么要使用异常 代码&#xff1a; public static void main(Sting args[]){int n1 1;int n2 0;System.out.println(n1/n2);//这里会抛ArihmaticException,因为除数不能为0,若未用异常处理机制则程序直接结束&#xff0c;后面的代码将不执行。这样很不好…

windows ipv4 多ip地址设置,默认网关跃点和自动跃点是什么意思?(跃点数)

文章目录 Windows中的IPv4多IP地址设置以及默认网关跃点和自动跃点的含义引言IPv4和IPv6&#xff1a;简介多IP地址设置&#xff1a;Windows环境中的实现默认网关跃点&#xff1a;概念和作用自动跃点&#xff1a;何时使用&#xff1f;关于“跃点数”如何确定应该设置多少跃点数&…

Docker 之 docker-compose.yml 配置项

指定 Eggjs 的容器运行环境&#xff0c;代替项目中的 env 配置文件 # 其他配置 web:# 其他配置environment:-EGG_SERVER_ENVprod # 此处指定了web容器的运行环境为prod, 等同于在Eggjs中env文件内设置成prod # 其他配置指定容器的网络&#xff0c;固定 IP # 其他配置 networks:…

我的创作纪念日(C++修仙练气期总结)

分享自己最喜欢的一首歌&#xff1a;空想フォレスト—伊東歌詞太郎 机缘 现在想想自己在CSDN创作的原因&#xff0c;一开始其实就是想着拿着博客当做自己的学习笔记&#xff0c;笔记嘛&#xff0c;随便写写&#xff0c;自己看得懂就ok了的态度凸(艹皿艹 )。也是用来作为自己学习…

vue3+element下拉多选框组件

<!-- 下拉多选 --> <template><div class"select-checked"><el-select v-model"selected" :class"{ all: optionsAll, hidden: selectedOptions.data.length < 2 }" multipleplaceholder"请选择" :popper-app…

【全链路追踪】XXL-JOB添加TraceID

文章目录 一、背景调用路径部署环境问题 二、方案三、Demo示例1、MDC2、RequestInterceptor3、HandlerInterceptor4、logback.xml 四、后续改进思路 一、背景 首先这个项目属于小型项目&#xff0c;由于人手以及时间限制&#xff0c;并未引入Skywalking等中间件来做调用链路追…

vite 项目搭建

1. 创建 vite 项目 npm create vite@latest 2. 安装sass/less ( 一般我使用sass ) cnpm add -D sasscnpm add -D less 3. 自动导入 两个插件 使用之后,不用导入vue中hook reactive ref cnpm install -D unplugin-vue-components unplugin-auto-import 在 vite.config.…

STM32设置为I2C从机模式(HAL库版本)

STM32设置为I2C从机模式&#xff08;HAL库版本&#xff09; 目录 STM32设置为I2C从机模式&#xff08;HAL库版本&#xff09;前言1 硬件连接2 软件编程2.1 步骤分解2.2 测试用例 3 运行测试3.1 I2C连续写入3.2 I2C连续读取3.3 I2C单次读写测试 4 总结 前言 我之前出过一篇关于…

Claude 2 国内镜像站

Claudeai是什么&#xff1f; Claude 2被称为ChatGPT最强劲的竞争对手&#xff0c;支持100K上下文对话&#xff0c;并且可以同时和5个文档进行对话&#xff0c;不过国内目前无法正常实用的&#xff0c;而claudeai是一个Claude 2 国内镜像站&#xff0c;并且免翻可用&#xff0…

Android 电池容量获取

Android 原生设置电池容量是在 power_profile.xml 中配置&#xff0c;此文件默认在 frameworks 目录下&#xff0c;也可能有 overlay 目录文件。 <!-- This is the battery capacity in mAh (measured at nominal voltage) --><item name"battery.capacity"…

实验三 HBase1.2.6安装及配置

系列文章目录 文章目录 系列文章目录前言一、HBase1.2.6的安装二、HBase1.2.6的配置2.1 单机模式配置2.2 伪分布式模式配置 总结参考 前言 在安装HBase1.2.6之前&#xff0c;需要安装好hadoop2.7.6。 本篇文章参考&#xff1a;HBase2.2.2安装和编程实践指南 一、HBase1.2.6的安…

Android---- 一个完整的小项目(消防app)

前言&#xff1a; 针对不同群体的需求&#xff0c;想着应该拓展写方向。医疗app很受大家喜欢&#xff0c;就打算顺手写个消防app&#xff0c;里面基础框架还是挺简洁 规整的。登陆注册和本地数据库写的便于大家理解。是广大学子的毕设首选啊&#xff01; 此app主要为了传递 消防…

python计算模板图像与原图像各区域的相似度

目录 1、解释说明&#xff1a; 2、使用示例&#xff1a; 3、注意事项&#xff1a; 1、解释说明&#xff1a; 在Python中&#xff0c;我们可以使用OpenCV库进行图像处理和计算机视觉任务。其中&#xff0c;模板匹配是一种常见的方法&#xff0c;用于在一幅图像中识别出与给定…

代码随想录打卡—day24—【回溯】— 基础最新8.20+8.22

1 理论基础 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯算法——回溯和递归是相辅相成的。回溯法的效率&#xff0c;回溯法其实就是暴力查找&#xff0c;并不是什么高效的算法。回溯法解决的问题都可以抽象为树形结构&#xff08;N叉树&#xff09; 1.1…

redis 7高级篇1 redis的单线程与多线程

一 redis单线程与多线程 1.1 redis单线程&多线程 1.redis的单线程 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理…

python-transformers基础总结【一】

2023年8月17日9:00:14 transformers模型只接受tensor作为输入,因此需要在在tokenizer的时候需要加参数return_tensors=“pt”,列表嵌套列表是不允许的。input_ids:将输入到的词映射到模型当中的字典IDattention_mask:是具有与input_ids张:量完全相同形状的张量,填充0和1。1…

【第三阶段】kotlin语言的内置函数let

1.使用普通方法对集合的第一个元素相加 fun main() {//使用普通方法对集合的第一个元素相加var list listOf(1,2,3,4,5)var value1list.first()var resultvalue1value1println(result) }执行结果 2.使用let内置函数对集合的第一个元素相加 package Stage3fun main() {//使用…

Android进阶之路 - 去除EditText内边距

正如题名&#xff0c;在Android中的EditText是自带内边距的&#xff0c;常规而言设置背景为null即可&#xff0c;但是因为使用了并不熟悉的声明式框架&#xff0c;本是几分钟解决的事儿&#xff0c;却花费了小半天~ 简单的需求&#xff0c;相关blog Android进阶之路 - 去除Edi…

探索智能文字识别:技术、应用与发展前景

探索智能文字识别&#xff1a;技术、应用与发展前景 前言一张图全览大赛作品解读随心记你不对我对小结 智能文字识别体系化解读图像预处理文字定位和分割文字区域识别图像校正字体识别和匹配结果后处理小结 如何应对复杂场景下挑战复杂场景应对方法小结 人才时代对人才要求合合…

MyBatis快速入门以及环境搭建和CRUD的实现

目录 前言 一、MyBatis简介 1.MyBatis是什么 2.MyBatis的特点 3.mybatis的作用 4.MyBatis的应用场景 5.MyBatis优缺点 二、相关概念 1.ORM概述 2.常见的ORM框架 3.什么是持久层框架 三、MyBatis的工作原理 1.框架交互 2.工作原理 ​编辑 四、MyBatis环境搭建 1…