golang编码最佳实践(持续更新中)
创始人
2024-11-15 10:36:16
0

最近在学习go语言,以此记录日常编码中的最佳实践,欢迎大家一起讨论

注释模板

使用Goanno插件:https://github.com/loveinsky100/goanno

设置模板

以goland为例

  1. 选择“工具-Goanno设置”

2. 编辑模板

// ${function_name} ${todo} //  @receiver ${receiver} //  @param ${params} //  @return ${return_types}

案例

// GetNode 获取指定结构,从缓存加载 // //	@receiver c //	@param ctx //	@param countryCode	国家码 //	@return *Node func (c *Cache) GetNode(ctx context.Context, countryCode string) *Node, error { 	... 	return cacheNode, nil }

 日志打印

建议使用zap的零内存分配api,性能比go标准库的api好:GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.

格式处理

  • %v: 根据值的类型自动选择合适的格式输出。
  • %+v: 类似 %v,但会输出更多的信息,如字段名称。
  • %#v: 输出值的完整 Go 语法表示,包括类型信息。
  • 不要直接打印byte[]类型等无实际意义的日志,应该转成string打印。

错误处理

go最受争议的部分之一就是错误处理,这里不去讨论其好坏,仍然推荐大家使用官方的处理方式:返回error接口,不建议使用panic + recover,容易导致程序崩溃

func f() error {     if ... {         return errors.New("xxx")     }     return nil }

解决error无堆栈

部分goer建议使用panic + recover就是因为其有堆栈而error没有,但我们可以实现新的error接口时增加堆栈记录

type BizError struct { 	code    string 	message string 	cause   error  	stacktrace struct { 		onCreate *stacktrace 		onPanic  *stacktrace 	} }  // NewBizError create a new BizError //   - cause can be nil is no underlying error //   - omitStacks indicates how many frames should be dropped, if <=0 no stacks will be filled func NewBizError(code api.ResultCode, message string, cause error, omitStacks int, throwInPlace bool) *BizError { 	err := &BizError{ 		code:    code.Code(), 		message: message, 		cause:   cause, 	} 	if omitStacks >= 0 { 		err.stacktrace.onCreate = dumpStacktrace(omitStacks + 1) 		if throwInPlace { 			err.stacktrace.onPanic = err.stacktrace.onCreate 		} 	} 	return err }  func (this *BizError) Error() string { 	if len(this.message) > 0 { 		return this.code + ": " + this.message 	} else { 		return this.code + ": [NO_MESSAGE]" 	} }  type stacktrace struct { 	header []byte // goroutine header (e.g., "goroutine 3 [running]:") 	frames []byte // the stacktrace details }  func dumpStacktrace(skip int) *stacktrace { 	skip += 2 // skip debug.Stack and this frame 	skip *= 2 // 2-line each frame 	stack := debug.Stack() 	var header []byte 	for i, b := range stack { // assumes no unicode in stack, iterate on bytes 		if b == '\n' { 			if header == nil { 				// consume first line as goroutine header 				header = stack[:i] 			} else { 				skip-- 				if skip == 0 { 					stack = stack[i:] 					break 				} 			} 		} 	} 	if skip > 0 { 		panic("skip overflow") 	} 	return &stacktrace{header, stack} }

这样使用时即可记录堆栈

类型转换

因为 Golang 语言是强类型,所以经常会使用到类型转换,所以在这里推荐类型转换三方库:GitHub - spf13/cast: safe and easy casting from one type to another in Go

cast.ToString("mayonegg")         // "mayonegg" cast.ToString(8)                  // "8" cast.ToString(8.31)               // "8.31" cast.ToString([]byte("one time")) // "one time" cast.ToString(nil)                // ""  var foo interface{} = "one more time" cast.ToString(foo)                // "one more time"  cast.ToInt(8)                  // 8 cast.ToInt(8.31)               // 8 cast.ToInt("8")                // 8 cast.ToInt(true)               // 1 cast.ToInt(false)              // 0  var eight interface{} = 8 cast.ToInt(eight)              // 8 cast.ToInt(nil)                // 0

json工具

golang原生对json已经做了很好的支持,简单易用,但其性能一直为人垢病,因此建议使用字节开源的工具sonic。除了能平替原生的json使用姿势外,在性能上面也是从底层方面做了很多文章进行优化,性能方面遥遥领先:sonic:基于 JIT 技术的开源全场景高性能 JSON 库_原生云_火山引擎开发者社区_InfoQ写作社区

web/rpc服务

单独为某个下游接口设置超时时间

要知道go的超时时间不像java那样每个调用都固定超时时间,而是以总体时间来计算,但有时部分下游就是需要超出原超时时间进行(当然必须是异步调用,否则就自相矛盾了),具体代码如下:

// 注意:必须先cancel再设置,否则只能设置比原来时间更短的时间 func TestTimeout(t *testing.T) { 	ctx := context.Background() 	ctx, _ = context.WithTimeout(ctx, time.Second*3) 	ctx, _ = context.WithTimeout(ctx, time.Second*6)  	deadline, _ := ctx.Deadline() 	fmt.Println(deadline.Sub(time.Now())) // 2.99s, 直接覆盖设置不生效  	ctx, _ = context.WithTimeout(ctx, time.Second*1) 	deadline, _ = ctx.Deadline() 	fmt.Println(deadline.Sub(time.Now())) // 0.99s 设置更短时间, 生效  	ctx = context.WithoutCancel(ctx)                 // 取消 	ctx, _ = context.WithTimeout(ctx, time.Second*6) // 重新设置 	deadline, _ = ctx.Deadline() 	fmt.Println(deadline.Sub(time.Now())) // 5.99s, 取消后再设置, 才生效 }

proto参数校验

建议使用PGV:GitHub - bufbuild/protoc-gen-validate: Protocol Buffer Validation - Being replaced by github.com/bufbuild/protovalidate

这样会再生成一个proto的validate文件

建议consumer在远程调用前先调用validate方法,当参数不合法时提前感知

func remote(ctx context.Context) {     request := ...     if err := request.ValidateAll(); err != nil { 		return nil, errors.New(err.Error()) 	}     remoteClient.GetXXX(ctx, request) }

provider在实现时也必须调用validate,防止参数不合法

func (s *server) Remote(ctx context.Context, request XXX) Response, error {     if err := request.ValidateAll(); err != nil { 		return nil, errors.New(err.Error()) 	}     // 处理逻辑 }

但每个方法都要加validate重复代码,有没有办法统一处理参数校验呢,当然是有的

type validator interface { 	ValidateAll() error }  // ValidateAllInterceptor // //	@Description: grpc服务注册的参数校验拦截器 //	@return grpc.UnaryServerInterceptor func ValidateAllInterceptor() grpc.UnaryServerInterceptor { 	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (reply interface{}, err error) { 		if v, ok := req.(validator); ok { 			if err := v.ValidateAll(); err != nil { 				return nil, err 			} 		} 		return handler(ctx, req) 	} }

相关内容

热门资讯

2秒钟猫腻!宝宝浙江游戏外挂i... 2秒钟猫腻!宝宝浙江游戏外挂ios(辅助挂)多乐红中麻将有挂的(2020已更新)(哔哩哔哩);1、完...
总算清楚(微信多乐跑胡子有挂的... 相信很多朋友都在电脑上玩过微信多乐跑胡子有挂的吧,但是很多朋友都在抱怨用电脑玩起来不方便。为此小编给...
热点推荐((微乐金花))外挂辅... 热点推荐((微乐金花))外挂辅助助手,太离谱了原来一直确实是有挂(有挂规律)(哔哩哔哩);最新版20...
四秒钟猫腻!牛气冲天游戏辅助器... 四秒钟猫腻!牛气冲天游戏辅助器(辅助挂)手机心悦麻将有挂的(2020已更新)(哔哩哔哩);亲真的是有...
七分钟实测!战神牛牛外挂(辅助... 七分钟实测!战神牛牛外挂(辅助挂)边锋老友外挂(2025已更新)(哔哩哔哩);超受欢迎的边锋老友外挂...
科技分享(广东麻将是有挂!太过... 科技分享(广东麻将是有挂!太过分了)原来真的确实是有挂(2023已更新)(哔哩哔哩);致您一封信;亲...
必看攻略((开心武汉花))外挂... 必看攻略((开心武汉花))外挂辅助脚本,太一般了原来确实真实是有挂(有挂助手)(哔哩哔哩);一、开心...
8分钟猫腻!缙云包尖有挂的(辅... 大家肯定在之前八一字牌辅助软件或者八一字牌辅助软件中玩过8分钟猫腻!缙云包尖有挂的(辅助挂)八一字牌...
玩家必看秘籍((指尖四川麻将)... 玩家必看秘籍((指尖四川麻将))外挂辅助插件,太嚣张了原来确实真实是有挂(有挂方式)(哔哩哔哩);指...
玩家必看分享(德扑之星的机制!... 玩家必看分享(德扑之星的机制!太夸张了)原来有辅助挂真的是有挂(2022已更新)(哔哩哔哩);德扑之...