7.8 error 错误接口
1. error 本质:标准库预定义接口
error是 Go 内置通用错误接口,仅定义一个Error()方法:
go
type error interface { Error() string }任何类型只要实现Error() string方法,就自动满足 error 接口,可作为错误返回值。
2. 快速创建错误:errors.New ()
- 导入包:
import "errors" - 底层原理
errors.New("xxx")内部返回*errorString结构体指针- 底层用指针而非结构体值:防止外部随意修改错误信息
- 每次调用 New 都会生成全新独立错误实例,错误之间不相等
- 使用示例
go
err := errors.New("参数不能为空") fmt.Println(err.Error()) // 调用方法输出错误文本3. 设计优势
统一所有函数错误返回规范,所有异常信息都可通过Error()获取字符串描述。
7.9 示例:表达式求值
本章配套实战案例,基于接口实现不同算术表达式统一求值逻辑,核心作用:
- 演示如何用接口抽象不同运算行为
- 结合 error 接口处理表达式语法、计算异常
- 体现接口抽象解耦不同运算类型的代码设计思想
7.10 类型断言 x.(T)
1. 基础定义
语法:接口变量.(类型T),称为类型断言作用:检测接口值内部的动态类型是否匹配目标类型 T,分两种场景。
场景 1:T 为具体类型
- 逻辑:校验接口动态类型是否等于 T
- 成功:提取接口内的动态值,类型转为 T
- 失败:直接触发 panic 崩溃
go
var w io.Writer = os.Stdout f := w.(*os.File) // 断言成功,拿到*os.File场景 2:T 为接口类型
- 逻辑:校验接口动态类型是否实现 T 接口
- 成功:生成新接口值,底层动态类型、动态值不变,仅对外暴露 T 接口的方法集
- 失败:panic
go
var r io.Reader = os.Stdin rw := r.(io.ReadWriter) // 若底层类型同时实现读写接口则成功2. 安全断言(双返回值,推荐)
单返回值断言失败会 panic,工程中使用value, ok := x.(T)安全写法:
ok=true:类型匹配,value 为对应值ok=false:类型不匹配,value 为 T 类型零值,不会崩溃
go
f, ok := w.(*os.File) if ok { // 执行文件专属逻辑 }3. 特殊规则
- 若接口值本身是 nil(动态类型、动态值全 nil),任何类型断言都失败
- 可复用原变量接收断言结果,覆盖原有变量值
7.11 基于类型断言区分细分错误
1. 基础错误判断函数(os 包)
文件操作提供快速判断错误类型工具函数,底层基于类型断言实现:
go
func IsExist(err error) bool // 文件已存在 func IsNotExist(err error) bool // 文件不存在 func IsPermission(err error) bool // 权限不足缺陷:不同操作系统底层错误描述不一致,仅简单字符串判断不够健壮。
2. 结构化错误类型(可靠方案)
标准库定义专用结构体存储完整错误上下文,比单纯字符串判断更精准:
os.PathError:单文件路径操作失败(Open/Delete)字段:操作名称、文件路径、底层原始错误os.LinkError:双路径操作失败(Symlink/Rename)
3. 正确区分错误流程
- 业务通用场景:直接调用
err.Error()打印统一错误文本 - 需要细分处理异常:使用类型断言提取底层结构化错误
go
if pe, ok := err.(*os.PathError); ok { fmt.Println("操作:", pe.Op, "路径:", pe.Path) }优势:直接读取结构化字段,不受系统错误文本差异影响。
7.12 类型分支 switch x.(type)
1. 接口两种使用场景
场景 A:行为抽象(主流)
代表类型:io.Reader、error、sort.Interface、http.Handler核心:接口定义统一行为方法,隐藏底层具体类型,关注行为而非类型。
场景 B:可辨识联合(Discriminated Unions)
接口仅作为多种类型的容器,依靠类型断言 / 分支区分底层类型,不隐藏细节,关注具体类型。典型场景:SQL 参数解析、多类型参数统一接收。
2. 类型分支语法
特殊 switch,switch 变量.(type),仅能在 switch 中使用type关键字:
go
switch v := data.(type) { case int: // v是int类型 case string: // v是string类型 case nil: // 接口值为nil default: // 其他所有类型 }- 自动提取对应类型值到分支内变量 v
- nil 分支匹配空接口值;default 匹配所有未命中类型
3. Go 接口设计最佳实践(核心面试考点)
- 不要提前定义仅单个实现的接口单一实现的接口属于多余抽象,带来额外运行时开销,直接导出类型方法即可。
- 接口使用的正确时机:至少两种具体类型需要统一处理
- 唯一例外:包解耦场景实现类型与接口分属不同包,无法直接依赖,用接口消除包依赖。
- 小接口优先设计原则:只定义需要的方法(ask only for what you need)如
io.Writer仅 1 个 Write 方法,小接口更容易被新类型实现,扩展性更强。
配套案例:SQL 参数解析
SQL 语句中?占位符需要接收布尔、数字、字符串、nil 多类型参数,使用类型分支区分不同参数类型,转换为标准 SQL 字面量,同时规避 SQL 注入风险。
本章总结
error是内置单方法接口,errors.New()快速生成错误,底层为指针结构体- 类型断言
x.(T)分具体类型 / 接口类型两种匹配逻辑,单返回值失败 panic,双返回值安全判断 - 区分 IO 错误优先使用结构化
PathError+ 类型断言,不依赖错误字符串判断 - 类型分支
switch x.(type)专门用于接口多类型分发,实现可辨识联合 - 接口两大用途:行为抽象、多类型容器
- 接口设计准则:多实现再抽象,优先小接口,不造单实现冗余接口