问题背景
在 Go 语言的开发中,错误处理是一个至关重要的部分,然而,错误处理的复杂性可能会导致意想不到的问题,尤其是当变量遮蔽出现时。Goland 本身并不会针对遮蔽变量进行报错,但在后续调用 make 命令编译时却会直接报错,报错信息截图如下:
代码分析
原报错部分代码
处理某个 RPC 客户端初始化时:
if filePath == "" || !(strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://")) {
logger.Ex(ctx, tag, fmt.Sprintf("file_path为空或资源请求不为http协议及https协议,需重新请求资源 req = %+v", req))
// 初始化RPC客户端
client, err := hk_manager.NewClient()
if err != nil {
logger.Ex(ctx, tag, fmt.Sprintf("初始化RPC失败 err:%+v", err))
return
}
if client == nil {
err = openerror.New(cerror.RpcUnavailable)
logger.Ex(ctx, tag, "RPC服务不可用 err:%+v", err)
return
}
// 调用RPC协议重新获取资源
rpcReq := &proto.RetryUploadMediaRequest{
MsgId: hkUserMsgInfo.MsgId,
ServerId: hkUserMsgInfo.ServerId,
Type: hkUserMsgInfo.ContentType,
}
rpcResp := &proto.RetryUploadMediaResponse{}
err = client.RetryUploadMedia(ctx, rpcReq, rpcResp)
if err != nil {
logger.Ex(ctx, tag, fmt.Sprintf("RPC调用失败 req = %+v, err = %v", req, err))
return
}
}
由于之前的代码已经定义了返回值 err
,导致 NewClient()
方法返回的错误被赋值给了 err
变量,这样做的结果是,函数的返回值 err
被 NewClient()
的错误所覆盖,导致我们无法在返回时获取到原始的错误信息
解决 bug 后的代码
if filePath == "" || !(strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://")) {
logger.Ex(ctx, tag, fmt.Sprintf("file_path为空或资源请求不为http协议及https协议,需重新请求资源 req = %+v", req))
// 初始化RPC客户端
client, rpcErr := hk_manager.NewClient()
if rpcErr != nil {
logger.Ex(ctx, tag, fmt.Sprintf("初始化RPC失败 err:%+v", rpcErr))
return
}
if client == nil {
rpcErr = openerror.New(cerror.RpcUnavailable)
logger.Ex(ctx, tag, "RPC服务不可用 err:%+v", rpcErr)
return
}
// 调用RPC协议重新获取资源
rpcReq := &proto.RetryUploadMediaRequest{
MsgId: hkUserMsgInfo.MsgId,
ServerId: hkUserMsgInfo.ServerId,
Type: hkUserMsgInfo.ContentType,
}
rpcResp := &proto.RetryUploadMediaResponse{}
err = client.RetryUploadMedia(ctx, rpcReq, rpcResp)
if err != nil {
logger.Ex(ctx, tag, fmt.Sprintf("RPC调用失败 req = %+v, err = %v", req, err))
return
}
关键改动说明:
-
引入新的错误变量
rpcErr
:在处理NewClient()
方法时,我们将错误存储在rpcErr
变量中,以避免覆盖函数的返回值err,
这样,err
变量仍然可以保留其他地方的错误信息 -
清晰的错误处理:通过区分不同的错误变量,我们可以更清楚地知道错误的来源,并确保在函数返回时可以准确地反映发生的错误
Go 语言的错误处理
在 Go 语言中,错误处理是编写健壮和可靠应用程序的核心,而与许多编程语言不同的是,Go 并不使用异常机制来处理错误,而是采用了显式的错误返回值,这种设计决定了错误处理的方式以及如何确保程序的健壮性(也是其设计哲学的一部分,这种设计理念有助于代码的简洁性和可读性),其 error 类型是一个内建的接口,定义如下:
type error interface {
Error() string
}
任何实现了 `Error() string` 方法的类型都可以被视为 `error`,这意味着我们可以通过返回一个实现了 `error` 接口的值来表示错误,并让调用者处理这些错误
在进行基本对错误处理时,调用者必须检查 `err` 是否为 `nil`,以决定如何处理错误,这种模式确保了每个函数都显式地处理可能发生的错误,增强了代码的可读性和健壮性
file, err := OpenFile("example.txt")
if err != nil {
//处理
}
defer file.Close()
怎样让错误处理的代码更清晰
一、错误信息的详细性
错误信息的详细性是决定错误处理有效性的关键,应该提供足够的上下文信息,以便在出现问题时能够快速诊断。例如,错误信息应包括导致错误的操作、相关的变量值、以及任何有助于调试的详细信息
二、错误的封装和传播
错误封装是一种在错误链中保留原始错误信息的技术,通过使用格式化字符串等可以将原始错误嵌入到更高层的错误中,从而提供更详细的上下文
三、避免错误的隐性传播
错误隐性传播是指在某个函数中发生的错误没有被明确处理或传播到调用链的根部,这可能会导致错误被忽视,从而影响程序的稳定性,确保每个函数都正确地处理和返回错误,可以避免这种情况
四、使用自定义错误类型
自定义错误类型可以包含更多的上下文信息,从而使错误处理更加精确
五、变量遮蔽和错误处理中的挑战
在复杂的函数中,尤其是那些涉及多个错误来源的函数,变量遮蔽(shadowing)可能会导致错误处理的混淆,变量遮蔽指的是在同一作用域中定义多个同名变量,从而导致错误变量的覆盖或丢失,在 Go 语言中,变量遮蔽是一个常见的问题,特别是在处理错误时。通过使用不同的变量名来处理函数内部的错误,我们可以避免错误信息的丢失或混淆,确保错误处理的准确性。这种做法不仅提高了代码的可读性,也使得错误调试更加明确,理解和解决这些问题对于编写健壮且易于维护的 Go 代码至关重要,在实际开发中,尤其是在涉及多个错误来源的情况下,确保错误变量不会冲突是一个重要的编码实践,通过这种方式,我们可以使代码更加可靠,并且在错误发生时能够准确地定位问题