@ -4,9 +4,9 @@
> < br >
> commit 83788ff212a3281328e2f8f223ce9e0f69220b97
为了改善我们的程序这里有四个问题需要修复,而且他 们都与程序的组织方式和如何处理潜在错误有关。第一,`main` 现在进行了两个任务:它解析了参数并打开了文件。对于一个这样的小函数,这并不是一个大问题。然而如果 `main` 中的功能持续增加,`main` 函数处理的独立任务也会增加。当函数承担了更多责任,它就更难以推导,更难以测试,并且更难以在不破坏其他部分的情况下做出修改。最好能分离出功能以便每个函数就负责一个任务。
为了改善我们的程序这里有四个问题需要修复,而且它 们都与程序的组织方式和如何处理潜在错误有关。第一,`main` 现在进行了两个任务:它解析了参数并打开了文件。对于一个这样的小函数,这并不是一个大问题。然而如果 `main` 中的功能持续增加,`main` 函数处理的独立任务也会增加。当函数承担了更多责任,它就更难以推导,更难以测试,并且更难以在不破坏其他部分的情况下做出修改。最好能分离出功能以便每个函数就负责一个任务。
这同时也关系到第二个问题:`query` 和 `file_path` 是程序中的配置变量,而像 `contents` 则用来执行程序逻辑。随着 `main` 函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将配置变量组织进一个结构,这样就能使他 们的目的更明确了。
这同时也关系到第二个问题:`query` 和 `file_path` 是程序中的配置变量,而像 `contents` 则用来执行程序逻辑。随着 `main` 函数的增长,就需要引入更多的变量到作用域中,而当作用域中有更多的变量时,将更难以追踪每个变量的目的。最好能将配置变量组织进一个结构,这样就能使它 们的目的更明确了。
第三个问题是如果打开文件失败我们使用 `expect` 来打印出错误信息,不过这个错误信息只是说 `Should have been able to read the file` 。读取文件失败的原因有多种:例如文件不存在,或者没有打开此文件的权限。目前,无论处于何种情况,我们只是打印出“文件读取出现错误”的信息,这并没有给予使用者具体的信息!
@ -29,7 +29,7 @@
* 调用 *lib.rs* 中的 `run` 函数
* 如果 `run` 返回错误,则处理这个错误
这个模式的一切就是为了关注分离:*main.rs* 处理程序运行,而 *lib.rs* 处理所有的真正的任务逻辑。因为不能直接测试 `main` 函数,这个结构通过将所有的程序逻辑移动到 *lib.rs* 的函数中使得我们可以测试他 们。仅仅保留在 *main.rs* 中的代码将足够小以便阅读就可以验证其正确性。让我们遵循这些步骤来重构程序。
这个模式的一切就是为了关注分离:*main.rs* 处理程序运行,而 *lib.rs* 处理所有的真正的任务逻辑。因为不能直接测试 `main` 函数,这个结构通过将所有的程序逻辑移动到 *lib.rs* 的函数中使得我们可以测试它 们。仅仅保留在 *main.rs* 中的代码将足够小以便阅读就可以验证其正确性。让我们遵循这些步骤来重构程序。
### 提取参数解析器
@ -51,7 +51,7 @@
我们可以采取另一个小的步骤来进一步改善这个函数。现在函数返回一个元组,不过立刻又将元组拆成了独立的部分。这是一个我们可能没有进行正确抽象的信号。
另一个表明还有改进空间的迹象是 `parse_config` 名称的 `config` 部分,它暗示了我们返回的两个值是相关的并都是一个配置值的一部分。目前除了将这两个值组合进元组之外并没有表达这个数据结构的意义:我们可以将这两个值放入一个结构体并给每个字段一个有意义的名字。这会让未来的维护者更容易理解不同的值如何相互关联以及他 们的目的。
另一个表明还有改进空间的迹象是 `parse_config` 名称的 `config` 部分,它暗示了我们返回的两个值是相关的并都是一个配置值的一部分。目前除了将这两个值组合进元组之外并没有表达这个数据结构的意义:我们可以将这两个值放入一个结构体并给每个字段一个有意义的名字。这会让未来的维护者更容易理解不同的值如何相互关联以及它 们的目的。
> 注意:一些同学将这种在复杂类型更为合适的场景下使用基本类型的反模式称为 ** 基本类型偏执**( *primitive obsession*)。
@ -66,7 +66,7 @@
< span class = "caption" > 示例 12-6: 重构 `parse_config` 返回一个 `Config` 结构体实例</ span >
新定义的结构体 `Config` 中包含字段 `query` 和 `file_path` 。
`parse_config` 的签名表明它现在返回一个 `Config` 值。在之前的 `parse_config` 函数体中,我们返回了引用 `args` 中 `String` 值的字符串 slice, 现在我们定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用他 们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
`parse_config` 的签名表明它现在返回一个 `Config` 值。在之前的 `parse_config` 函数体中,我们返回了引用 `args` 中 `String` 值的字符串 slice, 现在我们定义 `Config` 来包含拥有所有权的 `String` 值。`main` 中的 `args` 变量是参数值的所有者并只允许 `parse_config` 函数借用它 们,这意味着如果 `Config` 尝试获取 `args` 中值的所有权将违反 Rust 的借用规则。
还有许多不同的方式可以处理 `String` 的数据,而最简单但有些不太高效的方式是调用这些值的 `clone` 方法。这会生成 `Config` 实例可以拥有的数据的完整拷贝,不过会比储存字符串数据的引用消耗更多的时间和内存。不过拷贝数据使得代码显得更加直白因为无需管理引用的生命周期,所以在这种情况下牺牲一小部分性能来换取简洁性的取舍是值得的。
@ -76,7 +76,7 @@
我们更新 `main` 将 `parse_config` 返回的 `Config` 实例放入变量 `config` 中,并将之前分别使用 `query` 和 `file_path` 变量的代码更新为现在的使用 `Config` 结构体的字段的代码。
现在代码更明确的表现了我们的意图,`query` 和 `file_path` 是相关联的并且他 们的目的是配置程序如何工作。任何使用这些值的代码就知道在 `config` 实例中对应目的的字段名中寻找他 们。
现在代码更明确的表现了我们的意图,`query` 和 `file_path` 是相关联的并且它 们的目的是配置程序如何工作。任何使用这些值的代码就知道在 `config` 实例中对应目的的字段名中寻找它 们。
### 创建一个 `Config` 的构造函数
@ -142,9 +142,9 @@
现在 `build` 函数返回一个 `Result` ,在成功时带有一个 `Config` 实例而在出现错误时带有一个 `&'static str` 。回忆一下第十章 “静态生命周期” 中讲到 `&'static str` 是字符串字面值的类型,也是目前的错误信息。
` new ` 函数体中有两处修改:当没有足够参数时不再调用 `panic!` ,而是返回 `Err` 值。同时我们将 `Config` 返回值包装进 `Ok` 成员中。这些修改使得函数符合其新的类型签名。
` build ` 函数体中有两处修改:当没有足够参数时不再调用 `panic!` ,而是返回 `Err` 值。同时我们将 `Config` 返回值包装进 `Ok` 成员中。这些修改使得函数符合其新的类型签名。
通过让 `Config::build` 返回一个 `Err` 值,这就允许 `main` 函数处理 ` new ` 函数返回的 `Result` 值并在出现错误的情况更明确的结束进程。
通过让 `Config::build` 返回一个 `Err` 值,这就允许 `main` 函数处理 ` build ` 函数返回的 `Result` 值并在出现错误的情况更明确的结束进程。
#### 调用 `Config::build` 并处理错误
@ -214,7 +214,7 @@ Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在
#### 处理 `main` 中 `run` 返回的错误
我们将检查错误并使用类似示例 12-10 中 `Config::build` 处理错误的技术来处理他 们,不过有一些细微的不同:
我们将检查错误并使用类似示例 12-10 中 `Config::build` 处理错误的技术来处理它 们,不过有一些细微的不同:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -228,7 +228,7 @@ Rust 提示我们的代码忽略了 `Result` 值,它可能表明这里存在
### 将代码拆分到库 crate
现在我们的 `minigrep` 项目看起来好多了!现在我们将要拆分 *src/main.rs* 并将一些代码放入 *src/lib.rs* ,这样就能测试他 们并拥有一个含有更少功能的 `main` 函数。
现在我们的 `minigrep` 项目看起来好多了!现在我们将要拆分 *src/main.rs* 并将一些代码放入 *src/lib.rs* ,这样就能测试它 们并拥有一个含有更少功能的 `main` 函数。
让我们将所有不是 `main` 函数的代码从 *src/main.rs* 移动到新文件 *src/lib.rs* 中: