面试题答案
一键面试1. %w
解决的之前错误处理存在的问题
- 错误信息丢失:在之前,将一个错误包装成新错误时,原始错误的信息很容易丢失。例如,通过简单的字符串拼接构造新错误
fmt.Errorf("operation failed: %v", err)
,虽然包含了原始错误,但丢失了原始错误的类型和详细结构,这对于调试和区分不同类型错误很不利。 - 错误类型难以匹配:没有标准的方式来包装错误并保留原始错误类型,使得在调用栈上层难以通过
is
或as
关键字准确地匹配和处理特定类型的原始错误。
2. 正确使用错误包装与展开
- 错误包装:使用
fmt.Errorf("%w", err)
来包装错误。例如:
func readFile() error {
_, err := os.Open("nonexistentfile.txt")
if err != nil {
return fmt.Errorf("read file error: %w", err)
}
return nil
}
- 错误展开:
- 使用
errors.Is
检查特定错误:用于判断包装的错误链中是否包含某个特定错误。
- 使用
func main() {
err := readFile()
var pathError *os.PathError
if errors.Is(err, &os.PathError{}) {
// 处理路径相关错误
errors.As(err, &pathError)
fmt.Println("Path error details:", pathError.Path)
}
}
- 使用
errors.As
获取特定类型错误:从错误链中提取特定类型的错误。
3. 结合 context
包在复杂并发场景下处理和传递错误
- 取消操作:
context
包的WithCancel
、WithTimeout
等函数创建的上下文对象可用于取消或超时控制。在并发操作中,当某个操作出错需要取消其他相关操作时,通过上下文的取消信号来通知其他协程。
func worker(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
// 执行任务
if someErrorCondition {
return fmt.Errorf("worker error")
}
}
}
return nil
}
- 传递错误:在不同协程间传递上下文,当一个协程出错时,将错误通过上下文或返回值传递给调用者。例如:
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var wg sync.WaitGroup
wg.Add(1)
var err error
go func() {
defer wg.Done()
err = worker(ctx)
}()
wg.Wait()
if err != nil {
fmt.Println("Received error:", err)
}
}
通过 context
包和错误包装机制,可以在复杂并发场景下高效且优雅地管理错误,确保错误能够正确传递、处理,并且在必要时能够及时取消不必要的操作。