我应该使用 %s 还是 %v 来格式化错误

gon*_*opp 2 string error-handling formatting go

双方%s%v可以用来在Go格式错误,而且似乎没有功能上的差异,至少在表面上。

我们在 Go 自己的工具中看到了两者。

cmd/go/internal/get/path.go 中return fmt.Errorf("malformed import path %q: %v", path, err)

cmd/go/internal/list/list.go 中base.Fatalf("%s", err)

我应该更喜欢一个吗?

Fli*_*mzy 8

我应该使用 %s 还是 %v 来格式化错误?

TL; 博士; 两者都不。使用%w的病例99.99%。在案件的其他0.001%,%v%s可能是“应该”的行为一样,当误差值除外nil,但没有保证。%vfor nilerrors更友好的输出可能是更喜欢的原因%v(见下文)。

现在了解详情:

使用%w代替%v%s

从 Go 1.13 开始(或更早,如果您使用golang.org/x/xerrors),您可以使用%w动词,仅用于error值,它包装错误,以便以后可以用 解开errors.Unwrap,这样就可以考虑用errors.Iserrors.As

唯一不合适的时候:

  1. 您必须支持旧版本的 Go,xerrors这不是一个选项。
  2. 您想创建一个独特的错误,而不是包装现有的错误。这可能是合适的,例如,如果您Not found在搜索用户时从数据库中收到错误,并希望将其转换为Unauthorized响应。但是,在这种情况下,您很少会将原始错误值与任何格式动词一起使用。

好的,那么%v%s呢?

文档中提供有关如何实施%s%v实施的详细信息。我已经突出显示了与您的问题相关的部分。

  1. 如果操作数是一个reflect.Value,则操作数被它所持有的具体值替换,并且打印继续下一个规则。

  2. 如果一个操作数实现了 Formatter 接口,它将被调用。Formatter 提供对格式的精细控制。

  3. 如果 %v 动词与 # 标志 (%#v) 一起使用并且操作数实现了 GoStringer 接口,则将调用该接口。

    如果格式(对于 Println 等隐式为 %v)对字符串有效(%s %q %v %x %X),则以下两条规则适用

  4. 如果操作数实现了错误接口,则将调用 Error 方法将对象转换为字符串,然后根据动词(如果有)的要求对其进行格式化。

  5. 如果操作数实现方法 String() string,则将调用该方法将对象转换为字符串,然后将根据动词(如果有)的要求对其进行格式化。

总而言之,这些fmt.*f功能将:

  1. 寻找一个Format()方法,如果它存在,他们就会调用它。
  2. 寻找一个Error()方法,如果它存在,他们就会调用它。
  3. 寻找一个String()方法,如果它存在,就调用它。
  4. 使用一些默认格式。

所以在实践中,这意味着%s%v是相同的,除非Format()错误类型上存在方法(或错误是nil)。当错误确实有Format()方法时,人们可能希望它会产生与%s,%v和相同的输出err.Error(),但由于这取决于错误的实现,因此没有保证,因此这里没有“正确答案”。

最后,如果您的错误类型支持%+v动词变体,那么您当然需要使用它,如果您需要详细的输出。

nil 价值观

虽然这是罕见的(故意)调用fmt.*f上的nil错误,行为确有不同之间%s%v

%s: %!s(<nil>)
%v: <nil>
Run Code Online (Sandbox Code Playgroud)

游乐场链接


p9s*_*9sh 3

用于%v错误值。

if err != nil {
    return fmt.Errorf("pack %v: %v", name, err)
}
Run Code Online (Sandbox Code Playgroud)

但是, In Go 1.13,该fmt.Errorf函数支持新%w动词。当这个动词存在时,返回的错误fmt.Errorf将有一个Unwrap返回参数的方法%w,这一定是一个错误。在所有其他方面,%w与 相同%v

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("pack %v: %w", name, err)
}
Run Code Online (Sandbox Code Playgroud)

需要区分%w和的地方%v

阅读代码块中的注释

f, err := os.Open(filename)
if err != nil {
    // The *os.PathError returned by os.Open is an internal detail.
    // To avoid exposing it to the caller, repackage it as a new
    // error with the same text.
    //
    //
    // We use the %v formatting verb, since
    // %w would permit the caller to unwrap the original *os.PathError.
    return fmt.Errorf("%v", err)
}
Run Code Online (Sandbox Code Playgroud)

阅读:对于错误,我什么时候应该切换到 w


此外,内置的错误接口允许 Go 程序员添加他们想要的任何信息。它所需要的只是一个实现Error方法的类型

例子:

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }
Run Code Online (Sandbox Code Playgroud)

因此,大多数示例都有类似的实现类型,其中err有一个Error返回的方法string可供您使用%s