Go开发者常犯的错误,及使用技巧 (1)
代码规范
命名不规范
-
变量名要有意义,不能随便取a,b,c
- 如果只是纯粹的算法题,这样问题不大。但工程上的代码可读性要求较高,不能随意命名变量名,例如:
for _, v := range userList {// ...
}
如果for语句块简短还好,如果很长,顺着读下去在后面可能都不记得v代表什么,如果将v换成user,可读性会增加很多
不要说别人读自己的代码,就算自己隔几个月读自己的代码,如果可读性不高也会增加回忆时间
-
变量名和实际含义要相同
- 变量名需要和变量本身的意义相同,不能代码看起来是userIds,实际上装的是userInfos。这个问题比变量名没有意义更严重,因为对阅读代码的人产生误导
- 常发生于随着代码迭代,修改了变量的内容而忽略了修改变量名
-
方法名需要具体,不能用含糊不清的
handleXXX
,dealXXX
- 例如:处理试卷格式转换的方法,如果命名
handleExam
,使用者就无法通过方法名明确的知道该方法要干啥,还需要深入方法内部阅读详细逻辑,徒增理解成本 - 推荐做法:将笼统的handleExam改为具体的
TransferExamFormat
- 例如:处理试卷格式转换的方法,如果命名
-
方法名含义要清晰
- 例如:如果方法内部从缓存中获取,最好在方法名加上
FromCache
,提醒调用者可能使用的是过期的数据
- 例如:如果方法内部从缓存中获取,最好在方法名加上
-
名称啰嗦:
- 例如包名叫dingding,如果接口名再叫dingdngRobotService,调用者使用时的代码就是:
dingding.dingdingRobotService.XXX
,就会显得啰嗦 - 解决方法:接口名不要和包名冗余,改为
dingding.RobotService
- 例如包名叫dingding,如果接口名再叫dingdngRobotService,调用者使用时的代码就是:
必要的注释
如果代码质量好,可读性高,其实不需要什么注释
如果有特殊逻辑,需要用注释交代清楚这么做的原因
一旦加了注释,就需要随时保证注释和代码内容一致,不要因为产品迭代修改了代码,而忽略了修改注释
魔法数字
这个问题在比较常见,有时候为了图方便,会写出if XXX.status == 4 之类的代码,这必然会变成维护人员的噩梦
因为根本不知道4代表什么含义,只能尝试从各种文档,数据库改字段的注释中找,十分不方便
解决方法很简单,在const文件中集中维护这些特殊变量,例如:下面定义人教版的常量
const EditionRenjiao = 100
这样有以下好处:
- 增加可读性:从常量名就知道其含义
- 解耦:代码中其实不需要关心人教版的常量值是多少,只需要知道使用的是人教版,这里将人教版这个概念和这个概念具体的值接偶,这样当以后如果值变动,代码侧也不需要修改
降低包的表面积
不要都放到一个常量文件中,导致其变得很臃肿,还容易造成命名冲突
- 除了通用的常量外,应该各个模块分别维护一个常量文件(包),因为如果a模块不依赖b模块,那么a模块不需要也不应该使用b模块的常量
- 甚至不同层之间的常量也可以区分,因为
service
层的逻辑不应该了解dao
层自己使用的常量
不应该夸层依赖
如果a依赖b,b依赖c,则a不应该直接依赖c
例如:dao层依赖的gorm框架返回了gorm.RecordNotFound错误,dao层给service层要么返回nil,要么返回dao层自己的RecordNotFound错误,因为如果service层依赖了gorm的东西, 当dao层修改orm框架时,service层也需要修改,改动幅度较大。而如果service层只依赖dao层,那么变动只会影响dao层
函数太长
函数太长会导致可读性下降,难以维护
怎么解决呢?将方法根据功能进行拆分
func ABCDE() {ABCDE
}拆分为:func ABCDE() {A()B()C()D()E()
}
看起来就清晰多了,如果本身有上千行,进行划分之后每个函数也不会太长
而且每个小函数也方便写单测,容易提高代码质量
将函数在一屏幕展示完比较好,或者61行内
其次,同一个方法内不同部分也可以用空行分割,提高可读性
参数列表过长
当参数列表过长时会造成可读性下降,因此建议:
- 参数大于6个就可以封装为req结构体这样以后要新增参数,不用修改方法签名,特别是函数调用层级很深的场景下,能降低不少改动量
- 使用option模式
If else多层嵌套
func GetEnvName() string {if IsOnline() {return "online"} else {if IsLocalHost() {return "localhost"} else {return "test_" + GetShipName()}}
}
这样会导致else部分的层级很深,修改成下面:
func GetEnvName() string {if IsOnline() {return "online"}if IsLocalHost() {return "localhost"}return "test_" + GetShipName()
}
这两种实现的代码逻辑一样,但下面的实现方式通过提早返回,减少了很多else分支,使得每个逻辑的层级都不深,读起来十分清爽
并发问题
对map的并发使用
go的map不是线程安全的大家都知道,但有时候不是主动调用,而是依赖的库使用了map,我们并不知道,我们并发 使用了依赖的库,例如:
func ProcessWithHandle(ctx *gin.Context, req *dto.Req) (*http.Result, error) {// ...var redisKey = ctx.Request.Header.Get(defines.HeaderCacheKey)// ,,,
gohttp的header使用的普通map,因此不能并发使用
// A MIMEHeader represents a MIME-style header mapping
// keys to sets of values.
type MIMEHeader map[string][]string
怎么解决?使用gin.Context的set,get方法,有锁保护:
// Set is used to store a new key/value pair exclusively for this context.
// It also lazy initializes c.Keys if it was not used previously.
func (c *Context) Set(key string, value interface{}) {c.mu.Lock()if c.Keys == nil {c.Keys = make(map[string]interface{})}c.Keys[key] = valuec.mu.Unlock()
}
context问题
- context.Context 是 Go 语言中的一个标准类型,它内部往往包含跨越API,goroutine进程,进程边界的安全证书、trace信息、超时时间和取消信号等信息。Go 程序在整个过程的函数调用链中显式地传递 Context
- 建议在所有方法的第一个参数位置都加上context,就算当前不用也建议加上,因为如果以后扩展,例如加上打印日志的逻辑时如果没有ctx,该条日志就会从请求链路中断掉
- context不应该作为某个结构体的字段,因为context应该只和该次请求相关,请求结束,context的生命周期也应该结束
panic相关
panic造成的问题
-
如果没有用recover处理panic,会直接结束go进程
-
直接结束进程有什么问题?
- 无法进行优雅关闭,正在处理中的用户请求会失败,损失用户体验
- 后台的资源关闭无法进行,例如需要在优雅关闭回调中将本地缓存持久化的操作
- 如果只有一台实例,会造成短时间内服务不可用,直到拉起新的实例为止
-
一般发生在业务代码自己开的goroutine中,业务代码本身的panic会被框架(例如gin)的recover中间件兜底处理
-
func gopanic(e interface{}) {// ...preprintpanics(gp._panic)fatalpanic(gp._panic) // 结束进程:*(*int)(nil) = 0 // not reached}
- 推荐做法:封装工具方法SafeGo
func SafeGo(ctx context.Context, fn func()) {go func() {defer func() {if err := recover(); err != nil {// 记录日志,发送报警 }}()fn()}()
}
- 就算用recover处理了,例如中间件中,一般也是兜底的处理,只能给调用方返回系统错误之类的返回。因此如果提前识别出panic,可以根据不同情况返回不同的错误提示
可能出现panic的场景
-
操作nil对象:
- 常见于
user.XXX.XXX.XXX
之类的使用模式 - 推荐做法:需要确保中间每个环节的对象都不为nil
- 常见于
-
类型断言:
str := value.(string)
:这种断言调用,如果value不是string类型就会panic- 推荐做法:使用安全的断言方式:
str, ok := value.(string)
-
slice操作不当:
- 例如slice类型的
a
的len为0,却用a[0]
取数据
- 例如slice类型的
推荐使用panic的场景
go推荐通过err来处理错误,panic则用于处理预期之外,或十分严重的错误
例如:
-
项目启动时,如果各种组件,依赖的client初始化失败推荐panic直接终止进程,因为如果启动起来,就是不符合预期的代码在跑,可能造成问题,还不如提早把错误暴露出来
- 三方依赖包内不要在内部panic,而是返回err,交给业务代码决定要不要panic
-
MustXXX系列方法:
- 例如
MustConvertString2Int64(v string)
,MustGetUserId(ctx context.Context)
- 这类方法为了方便使用不会返回err,如果内部有err只能panic处理,因此需要保证使用时一定能成功,例如在前面进行了参数检查,那么在后面的流程中调用MustConvert就一定能成功
- 例如
栈溢出问题
只要没有递归调用,go其实不容易出现栈溢出的问题,因为栈深度最大能扩到1G
但一旦出现递归调用,且递归逻辑有bug,或者使用的数据配合递归逻辑有可能造成无限递归,就会出现栈溢出
栈溢出在go中十分严重,因为会直接结束进程
解决方法:
- 检查递归逻辑,确保没有bug
- 调用递归对使用的数据进行环检测,如果检测出环不能调用递归函数
错误处理
不能忽略错误
大部分情况下,错误一定不能忽略,因为如果忽略了,例如:
_ = redisClient.Set(ctx, key, value)
这样如果操作失败,不会在日志中体现,也不会返回给调用方,就默默吞掉错误
即:可能操作不成功,但给用户返回了成功,产生误导
且后面排查问题时由于没有日志记录,无法确认到底是不是因为这里没有操作成功
因此大部分情况下错误一定要处理,通常是要么返回给调用者,要么记录日志
如果出现错误了大部分情况下需要返回给调用者,否则后面的代码会使用错误/不完整的结果,除非明确知道这里返回的错误对后面的流程无影响
为什么说大部分?少部分情况下不用处理,例如json.Marshal
,该方法一般不会返回err,不处理的话可以减少代码量
只应该处理一次错误
错误只应该被处理一次,例如不能在调用链上,每次出现错误都记录日志
if err != nil {log.Errorf(ctx, "doXXX fail, err = %v", err)return err
}
这样的话有多少次方法调用,就会打印多少次日志,产生大量冗余日志,干扰问题排查
推荐做法:没有特殊情况内部方法只管返回,在入口处记录err日志
错误中应该携带足够的上下文
如果记录的日志中没有足够的上下文信息,则该日志价值很低,一般来说需要携带的信息有:
- 发生错误的调用栈快照:使用
errors.wrap
- 栈顶函数的部分参数信息,局部变量信息:使用
errors.WithMessage
什么时候携带栈信息,什么时候携带其他信息呢?
-
一般在最里层用wrap或stack包装
- 什么是最里层,即没有再进行方法调用,或调用外部依赖的那一层
- 调用当前项目的其他包不算最里层
-
上层要么直接返回最里层的err,要么用withMessage添加一些当前层的信息
-
相应的,如果自己开发第三方库,不应该返回堆栈信息
- go标准库就是这个做法
- 业务代码也不该关心三方库的调用堆栈
怎么判断是否携带了足够的上下文信息?
看根据该条日志能否排查出问题
什么时候返回err
公开方法基于扩展性考虑最好加一个err作为返回值,即使当前认为不需要返回err
相关文章:
Go开发者常犯的错误,及使用技巧 (1)
代码规范 命名不规范 变量名要有意义,不能随便取a,b,c 如果只是纯粹的算法题,这样问题不大。但工程上的代码可读性要求较高,不能随意命名变量名,例如: for _, v : range userList {// ... }如果for语句块简短还好&…...
Servlet 作业
一、填空题1. Servlet 中使用Session 对象的步骤为:调用HttpServletRequest.getSession()的得到Session对象,查看Session对象,在会话中保存数据。2. http 全称是_HyperText Transfer Protocol3. 用户可以有多种方式请求Servlet,如…...
Hive高阶函数:explode函数、Lateral View侧视图、聚合函数、增强聚合
Hive高阶函数 文章目录Hive高阶函数explode函数Lateral View侧视图原理语法聚合函数增强聚合grouping setsCUBEROLL UPexplode函数 explode接收map、array类型的数据作为输入,然后把输入数据中的每个元素拆开变成一行数据,一个元素一行。explode执行效果…...
信息系统服务管理
一、信息系统服务业及发展二、信息系统工程监理的概念及发展三、信息系统运行维护的概念和发展 IT服务管理(ITSM) 四、信息技术服务管理的标准和框架 IT服务标准体系(ITSS) 一、信息系统服务业及发展 总结:前景很好 二、信息系…...
Windows10 安装ElasticStack8.6.1
一、安装ElasticSearch8.6.1 1.官网下载ElasticSearch8.6.1压缩包后解压 2.安装为服务 elasticsearch-service.bat install 3.运行 elasticsearch-service.bat start 4.通过浏览器访问 http://localhost:9200/ 提示需要登录,但不知密码是啥。 5.重置密码 ela…...
gRPC 非官方教程
一、 简介 gRPC的定义: 一个高性能、通用的开源RPC框架主要面向移动应用开发: gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。基于HTTP/2协议标准而设计,基于ProtoBuf(Protoc…...
6.2【人工智能与深度学习】RNN、GRU、远程服务管理、注意力、Seq2 搜索引擎和内存网络
【人工智能与深度学习】RNN、GRU、远程服务管理、注意力、Seq2 搜索引擎和内存网络底层原理介绍 深度学习架构循环神经网络(RNN)循环网络:摊开循环的网络的循环循环神经网络的技巧乘法模组注意模组门控循环单元(GRU)长期短期记忆(Long Short-Term Memory,简称LSTM)序列到序列…...
软件工程复习
软件工程简介 软件: -在执行时提供所需的功能和性能的指令; -使程序能够充分操作信息的数据结构; -描述这些程序的操作和使用情况的文档。 软件定义:计算机程序和相关文档。 软件特点:软件没有质量;它并不…...
将Nginx 核心知识点扒了个底朝天(二)
Nginx 是如何实现高并发的? 如果一个 server 采用一个进程(或者线程)负责一个request的方式,那么进程数就是并发数。那么显而易见的,就是会有很多进程在等待中。等什么?最多的应该是等待网络传输。 而 Nginx 的异步非阻塞工作方…...
【PowerQuery】PowerBI 的PowerQuery支持的数据集成
PowerBI中的各个Power组件已经被深度集成到PowerBI中,不再作为像Excel一样的独立组件而存在。在PowerBI的界面中为了快速导入这些常用的数据,也有相应的快速导入界面。PowerBI的快速导入界面位于主页面中,下图就是PowerBI的快速导入界面。 在PowerBI中的数据导入界面相比Exc…...
scipy spatial transform Rotation库的源代码
前几日研究scipy的旋转,不知道具体里面怎么实现的,因此搜索一番。 发现Rotation在scipy的表达是用四元数的 https://github.com/jgagneastro/coffeegrindsize/edit/master/App/dist/coffeegrindsize.app/Contents/Resources/lib/python3.7/scipy/spatia…...
JAVA文件操作
JAVA文件操作 文章目录JAVA文件操作1.属性2.构造方法3.方法3.1创建文件3.2 文件删除3.3创建目录3.4文件名3.5 文件重命名3.6查看文件的可读性 Java中通过 java.io.file类来对文件(目录)进行抽象的描述。注意, 有File对象时,不代表真实存在该文件。1.属…...
字符串匹配 - 模式预处理:BM 算法 (Boyer-Moore)
各种文本编辑器的"查找"功能(CtrlF),大多采用Boyer-Moore算法,效率非常高。算法简介在 1977 年,Robert S. Boyer (Stanford Research Institute) 和 J Strother Moore (Xerox Palo Alto Research Center) 共…...
RV1126笔记三十:freetype显示矢量字体
若该文为原创文章,转载请注明原文出处。 在前面介绍了使用取模软件,可以自定义OSD,这种做法相对不灵活,也无法变更,适用大部分场景。 如果使用opencv需要移植opencv,芯片资源相对要相比好,而且移植比freetype复杂。 这里记录下如何使用freetype显示矢量字体,使用fre…...
polkit pkexec 本地提权漏洞修复方案
polkit pkexec 本地提权漏洞 漏洞细节,polkit pkexec 中对命令行参数处理有误,导致参数注入,能够导致本地提权。 解决建议 1、无法升级软件修复包的,可使用以下命令删除pkexec的SUID-bit权限来规避漏洞风险: chmod 0…...
es-06聚合查询
聚合查询 概念 聚合(aggs)不同于普通查询,是目前学到的第二种大的查询分类,第一种即“query”,因此在代码中的第一层嵌套由“query”变为了“aggs”。用于进行聚合的字段必须是exact value,分词字段不可进行…...
面试知识点准备与总结——(并发篇)
目录线程有哪些状态线程池的核心参数sleep和wait的区别lock 与 synchronized 的异同volatile能否保证线程安全悲观锁和乐观锁的区别Hashtable 与 ConcurrentHashMap 的区别ConcurrentHashMap1.7和1.8的区别ThreadLocal的理解ThreadLocalMap中的key为何要设置为弱引用线程有哪些…...
Django框架之模型视图-URLconf
URLconf 浏览者通过在浏览器的地址栏中输入网址请求网站对于Django开发的网站,由哪一个视图进行处理请求,是由url匹配找到的 配置URLconf 1.settings.py中 指定url配置 ROOT_URLCONF 项目.urls2.项目中urls.py 匹配成功后,包含到应用的urls…...
操作系统闲谈06——进程管理
操作系统闲谈06——进程管理 一、进程调度 01 时间片轮转 给每一个进程分配一个时间片,然后时间片用完了,把cpu分配给另一个进程 时间片通常设置为 20ms ~ 50ms 02 先来先服务 就是维护了一个就绪队列,每次选择最先进入队列的进程&#…...
DaVinci 偏好设置:用户 - UI 设置
偏好设置 - 用户/ UI 设置Preferences - User/ UI Settings工作区选项Workspace Options语言Language指定 DaVinci Resolve 软件界面所使用的语言。目前支持英语、简体中文、日语、西班牙语、葡萄牙语、法语、俄语、泰语和越南语等等。启动时重新加载上一个工作项目Reload last…...
Nacos超简单-管理配置文件
优点理论什么的就不说了,按照流程开始配配置吧。登录Centos,启动Naocs,使用sh /data/soft/restart.sh将自动启动Nacos。访问:http://192.168.101.65:8848/nacos/账号密码:nacos/nacos分为两部分,第一部分准…...
基于微信小程序的中国各地美食推荐平台小程序
文末联系获取源码 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.…...
如何优雅的导出函数
在开发过程中,经常会引用外部函数。方法主要有两种: 方法一:包含头文件并制定lib位置 优点:使用简单缺点:lib和vs版本有关,不同的版本和编译模式可能导致编译失败 方法二:GetProcAddress 优…...
c++多重继承
1.概论多重继承是否有必要吗?这个问题显然是一个哲学问题,正确的解答方式是根据情况来看,有时候需要,有时候不需要,这显然是一句废话,有点像上马克思主义哲学或者中庸思。但是这个问题和那些思想一样&#…...
15_FreeRtos计数信号量优先级翻转互斥信号量
目录 计数型信号量 计数型信号量相关API函数 计数型信号量实验源码 优先级翻转简介 优先级翻转实验源码 互斥信号量 互斥信号量相关API函数 互斥信号量实验源码 计数型信号量 计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在…...
二叉树(一)
二叉树(一)1.树的概念2.树的相关概念3.树的表示4.树在实际中的运用5.二叉树概念及结构6.特殊的二叉树7.二叉树的性质🌟🌟hello,各位读者大大们你们好呀🌟🌟 🚀🚀系列专栏…...
【SCL】1200案例:天塔之光数码管显示液体混合水塔水位
使用scl编写天塔之光&数码管显示&液体混合&水塔水位 文章目录 目录 文章目录 前言 一、案例1:天塔之光 1.控制要求 2.编写程序 3.效果 二、案例2:液体混合 1.控制要求 2.编写程序 三、案例3:数码管显示 1.控制要求 2.编写程序 3…...
5.1配置IBGP和EBGP
5.2.1实验1:配置IBGP和EBGP 实验目的 熟悉IBGP和EBGP的应用场景掌握IBGP和EBGP的配置方法 实验拓扑 实验拓扑如图5-1所示: 图5-1:配置IBGP和EBGP 实验步骤 IP地址的配置 R1的配置 <Huawei>system-view Enter system view, return …...
c++中超级详细的一些知识,新手快来
目录 2.文章内容简介 3.理解虚函数表 3.1.多态与虚表 3.2.使用指针访问虚表 4.对象模型概述 4.1.简单对象模型 4.2.表格驱动模型 4.3.非继承下的C对象模型 5.继承下的C对象模型 5.1.单继承 5.2.多继承 5.2.1一般的多重继承(非菱形继承) 5.2…...
[答疑]经营困难时期谈建模和伪创新-长点心和长点良心
leonll 2022-11-26 9:53 我们今年真是太难了……(此处删除若干字)……去年底就想着邀请您来给我们讲课,现在也没有实行。我想再和我们老大提,您觉得怎么说个关键理由,这样的形势合适引进UML开发流程? UML…...
国内做免费视频网站/广州各区最新动态
ORACLE SQL语句分类 SQL语句可以被看作是一种简单、强大的计算机语言或指令。sql语句被分为以下6类:数据定义类、数据操作类、事务控制类、会话控制、系统控制、嵌入式SQL语句。 数据操作语言语句[Data manipulation language,DML] 从一个或多个表或视图…...
承德网站建设规划/请你设计一个网络营销方案
文章目录前言一、用途1.捕获页面错误2.调试测试用例3.展示测试结果4.记录页面状态二、方法1. save_screenshot2. get_screenshot_as_file3. get_screenshot_as_png4. get_screenshot_as_base64总结前言 大家好,我是空空star,本篇给大家分享一下Selenium基…...
网站建设的界面风格有哪些/重庆网
论文作者: Enck, William Ongtang, MacHigar McDaniel, Patrick 下一代的开放操作系统不会在个人主机和大型主机上出现,而是在只能手机上。新环境的开放性将会催生许多新应用和市场,以及更大的集合环境。google的android系统是一个被寄予厚望…...
科普网站栏目建设方案策划/百度网站排名
在 C 语言标准(C89)没有定义布尔类型,所以 C 语言判断真假时以 0 为假,非 0 为真。 //就是1为ture,0为false int i1; int j0;https://www.runoob.com/note/34742...
网站左侧qq客服代码/苏州网站制作推广
首先说明一下,本人并没做过智能音箱类结构,至于为什么会写有关智能音箱相关的内容,主要原因是想通过自己总结下智能音箱类硬件结构的共性点以及注意点,以便日后能用得上,在写本篇之前,本人也拆解过自己的音…...
集团网站方案策划书/百度免费咨询
Vue2项目的创建和启动 创建项目 这个方式安装的是vue2的 cd 项目所在文件夹 ZHR:VueProjects zc$ vue init webpack learn? Project name learn ? Project description 学习 ? Author zhangyin ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to li…...