feat: rename Golang files to Chinese and supplement root files
Changes: - Renamed all 10 Golang files from English to Chinese names - Created 00-项目概述/项目概述.md with comprehensive project overview - Created 08-算法与数据结构/算法与数据结构学习指南.md with detailed learning guide - Created 12-面试技巧/面试准备进度.md with progress tracking - Added .obsidian configuration for better markdown editing - Updated Claude.MD with Chinese filename rule Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
408
questions/13-Golang语言/错误处理和测试.md
Normal file
408
questions/13-Golang语言/错误处理和测试.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# Golang 错误处理和测试
|
||||
|
||||
## 问题
|
||||
|
||||
1. Go 的错误处理机制是什么?为什么不用 try-catch?
|
||||
2. panic 和 recover 的使用场景是什么?
|
||||
3. errors.Wrap 和 errors.Is 的作用是什么?
|
||||
4. Go 的单元测试如何写?有哪些最佳实践?
|
||||
5. table-driven test 是什么?
|
||||
6. Go 的基准测试(Benchmark)如何写?
|
||||
|
||||
---
|
||||
|
||||
## 标准答案
|
||||
|
||||
### 1. Go 的错误处理
|
||||
|
||||
#### **error 接口**
|
||||
|
||||
```go
|
||||
type error interface {
|
||||
Error() string
|
||||
}
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 错误是值,不是异常
|
||||
- 必须显式处理
|
||||
- 避免嵌套过深
|
||||
|
||||
---
|
||||
|
||||
#### **创建错误**
|
||||
|
||||
```go
|
||||
import "errors"
|
||||
|
||||
// 1. 简单错误
|
||||
err1 := errors.New("something went wrong")
|
||||
|
||||
// 2. fmt.Errorf
|
||||
err2 := fmt.Errorf("invalid user: %s", "alice")
|
||||
|
||||
// 3. 错误包装(Go 1.13+)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query failed: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **处理错误**
|
||||
|
||||
```go
|
||||
func readFile(path string) ([]byte, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read file failed: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. panic 和 recover
|
||||
|
||||
#### **panic**
|
||||
|
||||
**用途**:不可恢复的错误(程序无法继续运行)
|
||||
|
||||
```go
|
||||
// panic 示例
|
||||
func doPanic() {
|
||||
panic("something terrible happened")
|
||||
}
|
||||
|
||||
func main() {
|
||||
doPanic()
|
||||
fmt.Println("this will not print")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **recover**
|
||||
|
||||
**用途**:捕获 panic,防止程序崩溃
|
||||
|
||||
```go
|
||||
func safeExecute() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Println("recovered from:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
panic("oh no!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
safeExecute()
|
||||
fmt.Println("program continues")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **最佳实践**
|
||||
|
||||
1. **避免使用 panic**:除非是真正不可恢复的错误
|
||||
2. **recover 只在 defer 中使用**:其他地方无效
|
||||
3. **库代码不应 panic**:应该返回 error
|
||||
|
||||
---
|
||||
|
||||
### 3. 错误包装和判断
|
||||
|
||||
#### **errors.Wrap**
|
||||
|
||||
```go
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func queryDB() error {
|
||||
return errors.New("connection failed")
|
||||
}
|
||||
|
||||
func service() error {
|
||||
err := queryDB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("service failed: %w", err) // 包装错误
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := service()
|
||||
if err != nil {
|
||||
// 检查错误链
|
||||
if errors.Is(err, errors.New("connection failed")) {
|
||||
fmt.Println("is connection error")
|
||||
}
|
||||
|
||||
// 获取最底层的错误
|
||||
var connErr error = errors.New("connection failed")
|
||||
if errors.Is(err, connErr) {
|
||||
fmt.Println("connection error occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **自定义错误类型**
|
||||
|
||||
```go
|
||||
import "errors"
|
||||
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
func validate(name string) error {
|
||||
if name == "" {
|
||||
return &ValidationError{Field: "name", Message: "required"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := validate("")
|
||||
var ve *ValidationError
|
||||
if errors.As(err, &ve) {
|
||||
fmt.Println("validation error:", ve.Field, ve.Message)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 单元测试
|
||||
|
||||
#### **基本测试**
|
||||
|
||||
```go
|
||||
package mypackage
|
||||
|
||||
import "testing"
|
||||
|
||||
func Add(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
result := Add(1, 2)
|
||||
if result != 3 {
|
||||
t.Errorf("Add(1, 2) = %d; want 3", result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Table-Driven Test(表格驱动测试)**
|
||||
|
||||
```go
|
||||
func TestAddTableDriven(t *testing.T) {
|
||||
tests := []struct {
|
||||
a, b int
|
||||
want int
|
||||
}{
|
||||
{1, 2, 3},
|
||||
{2, 3, 5},
|
||||
{10, -5, 5},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
|
||||
if got := Add(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **子测试(Subtests)**
|
||||
|
||||
```go
|
||||
func TestAll(t *testing.T) {
|
||||
t.Run("Test1", func(t *testing.T) {
|
||||
// Test1
|
||||
})
|
||||
|
||||
t.Run("Test2", func(t *testing.T) {
|
||||
// Test2
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 基准测试(Benchmark)
|
||||
|
||||
#### **基本 Benchmark**
|
||||
|
||||
```go
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Add(1, 2)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**运行**:
|
||||
```bash
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Benchmark 高级用法**
|
||||
|
||||
```go
|
||||
func BenchmarkAddParallel(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
Add(1, 2)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLargeSlice(b *testing.B) {
|
||||
b.ResetTimer() // 重置计时器
|
||||
data := make([]int, 1000000)
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = len(data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Mock 和测试替身
|
||||
|
||||
#### **使用接口进行 Mock**
|
||||
|
||||
```go
|
||||
type Database interface {
|
||||
Query(id int) (string, error)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db Database
|
||||
}
|
||||
|
||||
func (s *Service) GetUser(id int) (string, error) {
|
||||
return s.db.Query(id)
|
||||
}
|
||||
|
||||
// Mock 实现
|
||||
type MockDatabase struct {
|
||||
MockQuery func(id int) (string, error)
|
||||
}
|
||||
|
||||
func (m *MockDatabase) Query(id int) (string, error) {
|
||||
return m.MockQuery(id)
|
||||
}
|
||||
|
||||
// 测试
|
||||
func TestService_GetUser(t *testing.T) {
|
||||
mockDB := &MockDatabase{
|
||||
MockQuery: func(id int) (string, error) {
|
||||
return "Alice", nil
|
||||
},
|
||||
}
|
||||
|
||||
service := &Service{db: mockDB}
|
||||
name, err := service.GetUser(1)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if name != "Alice" {
|
||||
t.Errorf("got %s; want Alice", name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 测试最佳实践
|
||||
|
||||
#### **1. 测试覆盖率**
|
||||
|
||||
```bash
|
||||
# 查看覆盖率
|
||||
go test -cover
|
||||
|
||||
# 生成覆盖率报告
|
||||
go test -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **2. 测试隔离**
|
||||
|
||||
```go
|
||||
// 每个测试独立
|
||||
func Test1(t *testing.T) {
|
||||
// setup
|
||||
// test
|
||||
// teardown
|
||||
}
|
||||
|
||||
func Test2(t *testing.T) {
|
||||
// 不依赖 Test1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **3. 使用 TestMain**
|
||||
|
||||
```go
|
||||
func TestMain(m *testing.M) {
|
||||
// 全局 setup
|
||||
fmt.Println("setup before all tests")
|
||||
|
||||
m.Run()
|
||||
|
||||
// 全局 teardown
|
||||
fmt.Println("teardown after all tests")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. 阿里 P7 加分项
|
||||
|
||||
**深度理解**:
|
||||
- 理解 Go 错误处理的设计哲学(显式优于隐式)
|
||||
- 理解 panic/recover 的实现机制
|
||||
- 理解 table-driven test 的优势
|
||||
|
||||
**实战经验**:
|
||||
- 有编写高质量单元测试的经验(覆盖率 > 80%)
|
||||
- 有使用 Mock 进行测试的经验
|
||||
- 有编写 Benchmark 进行性能优化的经验
|
||||
|
||||
**测试策略**:
|
||||
- 理解如何设计可测试的代码(依赖注入、接口)
|
||||
- 理解如何进行集成测试和端到端测试
|
||||
- 理解如何设计测试用例(边界条件、异常情况)
|
||||
Reference in New Issue
Block a user