Rust语言 - 接口设计的建议之显而易见(Obvious)

王柳  金牌会员 | 2023-6-19 18:26:00 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 763|帖子 763|积分 2289

Rust语言 - 接口设计的建议之显而易见(Obvious)

显而易见(Obvious)

文档与类型系统


  • 用户可能不会完全理解接口的所有规则和限制

    • 重要:让用户容易理解接口,并难以用错

文档


  • 接口透明化的第一步:写出好的文档

  • 清楚的记录:

    • 可能出现意外的情况,或它依赖于用户执行超出类型签名要求的操作
    • 例:panic、返回错误、unsafe 函数...

如果你的代码可能会发生恐慌 panic,要把这一点记录到你的文档里,并且要记录在什么情况下,它会发生恐慌。
如果你的代码要返回错误,要把这一点记录到你的文档里,并且要记录在什么情况下,它会返回错误。
unsafe 函数,要在文档里写明需要满足什么条件才能安全的调用这个函数。
例子一:
  1. // Panic(恐慌)是这两种情况的一个很好的例子:如果代码可能发生 Panic,请在文档中明确说明这一点,以及可能导致 Panic 的情况。
  2. // 同样,如果代码可能返回错误,请记录它返回错误的情况。
  3. // 对于 unsafe 的函数,请记录调用者必须保证什么条件,才能确保调用时安全的。
  4. /// 除法运算,返回两个数的结果。
  5. ///
  6. /// # Panics
  7. ///
  8. /// 如果除数为零,该函数会发生 panic。
  9. ///
  10. /// # 示例
  11. ///
  12. /// ```
  13. /// let result = divide(10, 2);
  14. /// assert_eq!(result, 5);
  15. /// ```
  16. pub fn divide(dividend: i32, divisor: i32) -> i32 {
  17.   // 实现代码 ...
  18. }
复制代码

  • 在 crate 或 module 级,包括端到端的用例

    • 不是针对特定类型或方法,了解所有内容如何组合到一起
    • 对接口的整体结构有一个相对清晰的理解

      • 让开发者快速了解到各方法和类型的功能,以及在哪使用

    • 提供定制化使用的起点

      • 通过复制粘贴,结合需求进行修改


例子:查看标准库等相关文档

  • 组织好文档

    • 利用模块来将语义相关的项进行分组
    • 利用内部文档链接将这些项相互连接起来
    • 考虑使用 #[doc(hidden)] 标记那些不打算公开但出于遗留原因需要的接口部分,避免弄乱文档

例子二:
  1. /// 一个简单的模块,包含一些用于内部使用的函数和结构体。
  2. pub mod internal {
  3.   /// 一个用于内部计算的辅助函数。
  4.   #[doc(hidden)]
  5.   pub fn internal_helper() {
  6.     // 内部计算的具体实现 ...
  7.   }
  8.   
  9.   /// 一个仅用于内部使用的结构体。
  10.   #[doc(hidden)]
  11.   pug struct InternalStruct {
  12.     // 结构体的字段和方法 ...
  13.   }
  14. }
  15. /// 一个公共接口函数,调用了内部的辅助函数。
  16. pub fn public_function() {
  17.   // 调用内部辅助函数
  18.   internal::internal_helper();
  19. }
  20. /// 一个公共结构体,包含一些公共字段和方法。
复制代码

  • 尽可能的丰富文档

    • 可以链接到解释这些内容的外部资源:

      • 相关的规范文件(RFC)、博客、白皮书 ...

    • 使用 #[doc(cfg(..))] 突出显示仅在特定配置下可用的项

      • 用户能快速了解为什么在文档中列出的某个方法不可用

    • 使用 #[doc(alias = "...")] 可让用户以其他名称搜索到类型和方法
    • 在顶层文档中,引导用户了解常用的模块、Trait、类型、方法

例子三:
  1. //! 这是一个用于处理图像的库。
  2. //!
  3. //! 这个库提供了一些常用的图像处理功能,例如:
  4. //! - 读取和保存不同格式的图像文件 [`Image::load`] [`Image::save`]
  5. //! - 调整图像的大小、旋转和裁剪 [`Image::resize`] [`Image::rotate`] [`Image::crop`]
  6. //! - 应用不同的滤镜和效果 [`Filter`] [`Effect`]
  7. //!
  8. //! 如果您想了解更多关于图像处理的原理和算法,您可以参考以下的资源:
  9. //! - [数字图像处理](https://book.douban.com/subject/5345798/),一个经典的教科书,介绍了图像处理的基本概念和方法。
  10. //! - [Learn OpenCV](https://learnopencv.com/),一个网站,提供了很多用OpenCV实现图像处理功能的教程和示例代码。
  11. //! - [Awesome Computer Vision](https://github.com/jbhuang0604/awesome-computer-vision),一个GitHub仓库,收集计算机视觉资源。
  12. /// 一个表示图像的结构体
  13. #[derive(Debug, Clone)]
  14. pub struct Image {
  15.   // ...
  16. }
  17. impl Image {
  18.   /// 从指定的路径加载一个图像文件
  19.   ///
  20.   /// 支持的格式有:PNG、JPEG、GIF、BMP 等
  21.   ///
  22.   /// # 参数
  23.   ///
  24.   /// - `path`: 图像文件的路径
  25.   ///
  26.   /// # 返回值
  27.   ///
  28.   /// 如果成功,返回一个 [`Image`] 实例;如果失败,返回一个 [`Error`]。
  29.   ///
  30.   /// # 示例
  31.   ///
  32.   /// ```no_run
  33.   /// use image::Image;
  34.   ///
  35.   /// let img = Image::load("test.png")?;
  36.   /// ```
  37.   #[doc(alias = "读取")]
  38.   #[doc(alias = "打开")]
  39.   pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
  40.     // ...
  41.   }
  42.   
  43.   /// 将图像保存到指定的路径
  44.   ///
  45.   /// 支持的格式有:PNG、JPEG、GIF、BMP 等
  46.   ///
  47.   /// # 参数
  48.   ///
  49. }
复制代码
例子四:
  1. /// 一个只在启用了 `foo` 特性时才可用的结构体。
  2. #[cfg(feature = "foo")]
  3. #[doc(cfg(feature = "foo"))]
  4. pub struct Foo;
  5. impl Foo {
  6.   /// 一个只在启用了 `foo` 特性时才可用的方法。
  7.   #[cfg(feature = "foo")]
  8.   #[doc(cfg(feature = "foo"))]
  9.   pub fn bar(&self) {
  10.     // ...
  11.   }
  12. }
  13. fn main() {
  14.   println!("Hello, world!");
  15. }
复制代码
类型系统


  • 类型系统可确保:

    • 接口明显
    • 自我描述
    • 难以被误用

  • 语义化类型:

    • 添加类型来表示值的意义(不仅仅适用基本类型)

例子五:
  1. fn processDate(dryRun: bool, overwrite: bool, validate: bool) {
  2.   // 处理数据的逻辑
  3. }
  4. enum DryRun {
  5.   Yes,
  6.   No,
  7. }
  8. enum Overwrite {
  9.   Yes,
  10.   No,
  11. }
  12. enum Validate {
  13.   Yes,
  14.   No,
  15. }
  16. fn processData(dryRun: DryRun, overwrite: Overwrite, validate: Validate) {
  17.   // 处理数据的逻辑
  18. }
  19. processData(DryRun::Yes, Overwrite::No, Validate::Yes);
  20. fn main() {
  21.   println!("Hello, world!");
  22. }
复制代码

  • 使用”零大小“类型来表示关于类型实例的特定事实
例子六:
  1. struct Grounded;
  2. struct Launched;
  3. // and so on
  4. enum Color {
  5.   White,
  6.   Black,
  7. }
  8. struct Kilograms(u32);
  9. struct Rocket<Stage = Grounded> {
  10.   stage: std::marker::PhantomData<Stage>,
  11. }
  12. impl Default for Rocket<Grounded> {
  13.   fn default() -> Self {
  14.     Self {
  15.       stage: Default::default()
  16.     }
  17.   }
  18. }
  19. impl Rocket<Grounded> {
  20.   pub fn launch(self) -> Rocket<Launched> {
  21.     Rocket {
  22.       stage: Default::default(),
  23.     }
  24.   }
  25. }
  26. impl Rocket<Launched> {
  27.   pub fn accelerate(&mut self) {}
  28.   pub fn decelerate(&mut self) {}
  29. }
  30. impl<Stage> Rocket<Stage> {
  31.   pub fn color(&self) -> Color {
  32.     Color::White
  33.   }
  34.   pub fn weight(&self) -> Kilograms {
  35.     Kilograms(0)
  36.   }
  37. }
  38. fn main() {
  39.   println!("Hello, world!");
  40. }
复制代码

  • #[must_use] 注解

    • 将其添加到类型、Trait 或函数中,如果用户的代码接收到该类型或 Trait 的元素,或调用了该函数,并且没有明确处理它,编译器将发出警告

例子七:
  1. #[must_use]
  2. fn process_data(data: Data) -> Result<(), Error> {
  3.   // 处理数据的逻辑
  4.   
  5.   Ok(())
  6. }
  7. // 在这个示例中,我们使用 #[must_use] 注解将 process_data 函数标记为必须使用期返回值。
  8. // 如果用户在调用该函数后没有显式处理返回的 Result 类型,编译器将发出警告。
  9. // 这有助于提醒用户在处理潜在的错误情况时要小心,并减少可能得错误。
  10. fn main() {
  11.   println!("Hello, world!");
  12. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表