设计初衷
最重要的是,所有错误都应该有用。这是他们存在的唯一目的,否则我们将静默失败,让每个人都感到好奇究竟发生了什么。
错误应该提供用户采取适当操作所需的信息。有时该用户是代码中的 match
,有时是查看日志缓冲区的人类。以用户为中心是构建对他们有用的关键!
简而言之,如果您遵循以下原则:
- 错误将变得更有意义
- 错误更易于处理
- 组件管理器的代码库将更易于阅读
- 将避免更多 bug
- 会触发错误的 bug 更易于修复
原则
以下原则只是有关如何出现有意义且有用的错误的指南,但并不是很有指导意义。如果您认为某个场景中的这些准则是错误的,请运用您的最佳判断来更新此文档。
请勿不小心使用 panic!
、assert!
、unwrap(...)
和 expect(...)
组件管理器应该不会崩溃。如果组件管理器崩溃,系统会重新启动,且用户数据可能会丢失。
在极少数情况下,崩溃是可以接受的:
- 如果违反不可变性破坏了关于组件管理器行为的基本假设,则对内部不变量进行断言
- 这种做法可能不安全,可能会泄露用户数据或让攻击者继续利用。
- 示例:组件状态
- 示例:参数验证
- 示例:对系统其余部分的预期
- 示例:组件管理器配置
fidl::endpoints::create_*
方法可安全解封。它们会创建 zircon 通道,并等待这些创建的通道上的数据包。- 如果对已经过验证的不变体进行断言,可以生成更简洁的代码。
- 示例:检查
map.contains_key(key)
为 true,然后调用map.get(key).unwrap()
或map[key]
。 - 如果可能,最好使用
if let
或match
重写代码,这样就不需要解封/紧急呼叫。
- 示例:检查
请勿使用 println!
虽然 println!
与组件管理器中的调试日志集成,但 tracing log library
可提供更多功能,并在我们的代码库中应用更广泛。tracing
库还与 debuglog 集成,具有方便的宏,并且支持结构化日志记录。
println!
。此功能目前受 lint 规则保护。
请勿使用 anyhow
库
借助 anyhow
库,无法构建结构合理的错误类型并与之进行匹配。
此规则没有已知的例外情况。
务必使用 thiserror
库创建自定义错误类型
thiserror
库会自动针对自定义错误类型实现 std::error::Error
。避免手动实现 std::error::Error
。
此规则没有已知的例外情况。
务必为组件管理器中的特定功能/操作创建自定义错误类型
自定义错误类型有助于枚举功能/操作的所有可能错误状态。它们不会与组件管理器的任何其他区域相关联,并且可以独立于代码库的其余部分进行维护。
务必考虑是否有必要针对您的自定义错误类型实现 Into<ModelError>
ModelError 有许多针对不同错误类别的枚举变体。您可能根本不需要将自定义错误类型添加到 ModelError 中。
请勿向 ModelError
添加精确错误
最好创建自定义错误类型来表示您的地图项/操作的所有错误,即使您当前只有一个错误也是如此。如果将精确错误直接添加到 ModelError 中,开发者可能会失去关于错误适用位置的背景信息。
请勿存储 CloneableError
等一般性错误类型
宽泛的错误类型会导致无法构建结构合理的错误。没有惯用的方法可以匹配这些泛型类型。
此规则的唯一例外情况是错误来自使用通用错误类型的外部库。可能无法更改外部库,因此可以接受存储泛型。
thiserror
的 #[from]
属性用于一般错误类型。这样可以轻松地将任何错误隐式转换为一般性错误。
请改用 #[source]
并进行显式转换。请勿将现有错误类型用于“足够接近”的行为
在组件管理器中导致 bug 的根本原因在于,错误描述世界状态的错误会造成难以调试的问题,尤其是在组件管理器中。最好创建错误类型,以准确描述不同的错误状态。另一种方法是向现有错误类型添加枚举,以提供其他分类。
此规则没有已知的例外情况。
务必考虑日志记录是否绝对必要
可以将组件管理器视为一种超级库。作为一个库,组件管理器通常无法知道错误是否有意义。例如,请考虑如果 UDP 库记录每个丢弃的数据包,会发生什么情况。
虽然在此问题上还没有普遍接受的建议,但对于组件管理器,应遵循以下提示:
- 谨慎使用日志记录。
- 在提交之前,为协助开发/调试而创建的日志必须移除或设置为
DEBUG
日志级别。 - 避免在热代码路径中添加日志。这可能会产生日志垃圾内容,并且这种垃圾内容并无用处。
- 如果错误也通过 FIDL 返回给客户端,并且详细信息数量相同,请勿记录错误。
- 相反,如果通过 FIDL 发送错误时丢失了一些详细信息,可以记录详细的错误消息。
- 另外,请考虑客户端没有收到详细错误的原因。
- 记录未涉及客户端的错误,并且该错误可能对调试问题很重要。
- 遵循 RFC-0003 中设置的日志记录准则
务必将组件标识符添加到日志和错误中
组件管理器经常会产生与特定组件相关的错误。确保错误包含组件标识符,以便于调试。 对于日志,请使用组件级范围的日志记录器,或在消息中添加组件标识符。
可接受的组件标识符(按优先顺序排列):名称、组件网址、实例 ID
不要在日志消息中输出对象的 Debug
字符串
Debug
特征和相应的 {:?}
格式说明符只能用于交互式调试目的。将等效的 JSON 对象输出到日志会使它们更难以理解。倾向于将简单易懂的错误消息输出到日志。
此规则没有已知的例外情况。
请勿存储字符串化的错误消息
请勿存储错误的 Debug
或 Display
字符串。字符串错误没有可靠的结构,无法匹配。始终首选按原样存储错误。
外部库中的某些错误未实现 Clone
或 PartialEq
等所需的特征。在这类罕见情况下,如果无法将这些特征添加到外部库,可以将错误消息字符串化并存储。
请勿为每个错误变体创建函数
不需要为错误变体创建创建函数。它们允许某些类型的隐式转换,还会隐藏字段名称。最好直接创建错误类型并手动设置字段名称。
此规则没有已知的例外情况。
要像板箱作者一样思考
组件管理器是一个大型 crate。我们已将路线引入自己的箱子中 未来可能会提供更多功能思考一下,如果您处理的特定代码部分是其自身 crate 的一部分,那么您将会使用哪些错误类型。对于 crate 中的代码逻辑集合,合理的错误类型是什么?